০০ যে প্রশ্নগুলোর উত্তর খুঁজবো
সেশন ১১ এ আমরা দেখেছি, কেন একটা বড় application কে ছোট ছোট service এ ভাগ করা হয়। ভাগ করার পর একটা নতুন বাস্তবতা সামনে আসে। আগে যে কাজটা ছিলো একই program এর ভেতরে একটা function call, এখন সেটা network এর উপর দিয়ে আরেকটা machine এ যাওয়া একটা request।
Function call প্রায় কখনো হারায় না, আর মাঝপথে আটকেও থাকে না। Network call হারাতে পারে, ধীর হতে পারে, আবার অর্ধেক কাজ করে থেমেও যেতে পারে। এই সেশনে আমরা দেখবো, service গুলো কীভাবে একে অপরের সাথে কথা বলে, আর একটা service এর ছোট সমস্যা কীভাবে পুরো system কে থামিয়ে দিতে পারে। শেষে আপনি জানবেন, এই বিপদ ঠেকাতে প্রতিটা call এ কোন নিয়মগুলো মানতে হয়।
- ১ একটা screen এর জন্য service গুলো একে অপরকে কীভাবে ডাকে? (sync RPC বনাম async message)
- ২ REST নাকি gRPC? JSON নাকি Protobuf?
- ৩ API contract কী, আর API versioning ছাড়া একটা field বদলালে কী ভাঙে?
- ৪ Call গুলো series এ চললে latency chain কত লম্বা হয়?
- ৫ Call গুলো parallel এ fan-out করলে কতটা লাভ, আর tail latency কেন ভোগায়?
- ৬ একটা service ধীর হলে পুরো app কেন আটকে যায়? (partial failure, cascading failure)
- ৭ প্রতিটা call এ timeout কেন লাগে, আর মানটা কত হবে?
- ৮ Retry কখন নিরাপদ? (retry storm, idempotency)
- ৯ সব service এ একই নিয়ম কীভাবে চালু রাখবেন? (service mesh, sidecar)
০১ একটা screen এর জন্য service গুলো একে অপরকে কীভাবে ডাকে?
শুক্রবার সন্ধ্যা ছয়টা। অফিস ছুটির ভিড়ে ঢাকার রাস্তায় হাজার হাজার মানুষ ride খুঁজছে। সাকিব একটা ride-sharing app এর backend engineer। সে নিজেও বাড়ি ফেরার জন্য একটা bike ডাকতে যাচ্ছিলো, তখনই ফোনে alert বাজলো। Support team এর group এ একের পর এক message আসছে: “App খুলছে না”, “ভাড়া দেখাচ্ছে না”, “ride book হচ্ছে না”। সাকিব dashboard খুলে অবাক হলো। প্রায় সব service ঠিকঠাক চলছে, শুধু Pricing service টা একটু ধীর। একটা service একটু ধীর হলে পুরো app কেন বন্ধ হয়ে যাবে?
এই প্রশ্নের উত্তর পেতে আগে বুঝতে হবে, service গুলো একে অপরের সাথে কথা বলে কীভাবে। সাকিবের দলের সবচেয়ে ব্যস্ত screen টা দিয়েই শুরু করা যাক।
Trip Summary screen: একটা screen, ছয়টা service
Ride শেষ হলে rider একটা summary screen দেখে। সেখানে থাকে পথের map, ভাড়ার হিসাব, driver এর নাম আর ছবি, payment হয়েছে কিনা, rating দেওয়ার জায়গা, আর পরের ride এর একটা promo। এই প্রতিটা তথ্য আলাদা আলাদা service এর কাছে থাকে।
Monolith এর যুগে (সেশন ১১) এগুলো একই program এর ভেতরের function ছিলো। pricing.getFare(48213) লিখলে উত্তর আসতো কয়েক microsecond এ। এখন Pricing আলাদা machine এ চলা আলাদা একটা service। তাই একই লাইনের পেছনে এখন একটা network request যায়, আর উত্তর আসে network পেরিয়ে।
RPC: দেখতে function call, আসলে network call
অন্য machine এর একটা function কে নিজের code এর function এর মতো করে ডাকার কৌশলকে বলে RPC (Remote Procedure Call)। Code এ দেখতে সাধারণ function call, কিন্তু পেছনে request টা network দিয়ে যায়, আর উত্তরও network দিয়ে ফেরে।
এই মিলটাই সবচেয়ে বড় ফাঁদ। Function call এর ফল দুই রকম: উত্তর আসে, অথবা error আসে। Network call এর আরো একটা ফল আছে: কিছুই আসে না। Request টা পৌঁছেছে কিনা, কাজটা হয়েছে কিনা, caller সেটা জানতেও পারে না। এই সেশনের প্রায় সব সমস্যা এই তৃতীয় অবস্থা থেকেই আসে।
দুই রকম কথা বলা: sync আর async
Service গুলো দুইভাবে কথা বলতে পারে। পার্থক্যটা এক প্রশ্নে: যে ডাকছে, সে কি উত্তরের জন্য অপেক্ষা করে?
| Sync (RPC) | Async (message) | |
|---|---|---|
| Caller কি অপেক্ষা করে? | হ্যাঁ, উত্তর না আসা পর্যন্ত | না, message রেখেই নিজের কাজে ফেরে |
| কখন মানায় | উত্তর ছাড়া পরের ধাপ এগোয় না | শুধু জানিয়ে দিলেই যথেষ্ট |
| সাকিবের app এ উদাহরণ | Summary screen এর জন্য ভাড়া জানা | Trip শেষে রিসিট email, driver এর আয় আপডেট করা |
| অন্য পক্ষ down থাকলে | Caller এর কাজও আটকে যায় | Message queue তে অপেক্ষা করে, পরে process হয় |
Async এর কৌশল আমরা আগেই শিখেছি: queue আর ACK (সেশন ০৮), আর event দিয়ে অনেক service কে একসাথে জানানো (সেশন ১০)। এই সেশনের বেশিরভাগ জুড়ে থাকবে sync call। কারণ rider screen এর দিকে তাকিয়ে আছে, তার উত্তর এখনই দরকার।
নিজেকে একটা প্রশ্ন করুন: উত্তরটা হাতে না পেলে কি আমার পরের কাজ এগোবে? না এগোলে sync call দিন। শুধু খবরটা পৌঁছে দিলেই চললে message দিন। যত বেশি কাজ async এ সরানো যায়, একটা service এর ধীরতা তত কম অন্যদের আটকায়।
০২ REST নাকি gRPC? JSON নাকি Protobuf?
Sync call করার সবচেয়ে প্রচলিত দুইটা পথ হলো REST আর gRPC।
REST আর JSON
REST আপনি আগে থেকেই চেনেন, হয়তো নামটা না জেনেই। HTTP এর পরিচিত method (GET, POST) দিয়ে একটা URL এ request পাঠানো হয়, আর উত্তর আসে JSON এ। যেমন GET /fares/48213 পাঠালে Pricing একটা JSON ফেরত দেয়। JSON হলো text, তাই মানুষ চোখে পড়তে পারে। Browser, curl, Postman, সবাই REST বোঝে।
gRPC আর Protobuf
gRPC হলো Google এর তৈরি একটা open-source RPC framework। এখানে আপনি প্রথমে একটা .proto file এ লিখে রাখেন, service টার কোন কোন function আছে আর প্রতিটা message দেখতে কেমন। যেমন: rpc GetFare(FareRequest) returns (FareReply);। তারপর একটা tool সেই file থেকে Go, Java, Python বা অন্য ভাষায় client আর server এর code বানিয়ে দেয়। Summary তখন সত্যিই একটা function এর মতো pricingClient.GetFare(...) ডাকে।
gRPC এর data যায় Protobuf (Protocol Buffers) format এ। এটা text নয়, binary format। প্রতিটা field এর নাম প্রতিবার না পাঠিয়ে শুধু তার একটা ছোট নম্বর পাঠানো হয়। নম্বর দেখে কোন field, সেটা দুই পক্ষই .proto file থেকে জানে। নিচে একই উত্তর দুই format এ দেখুন:
// JSON (REST এ সাধারণ): প্রতিবার field এর নামসহ text যায়
{"trip_id":48213,"fare_taka":285,"distance_km":7.4,"surge":1.2}
size: 63 bytes
// Protobuf (gRPC এ): shape টা একবার .proto file এ লেখা থাকে
message FareReply {
int64 trip_id = 1;
int32 fare_taka = 2;
float distance_km = 3;
float surge = 4;
}
// wire এ যায় শুধু field number আর binary value, নাম যায় না
size: 17 bytes (প্রায় ৪ গুণ ছোট)
একটা call এ ৪৬ byte কম মনে হতে পারে সামান্য। কিন্তু সাকিবের system এ সন্ধ্যার peak এ প্রতি সেকেন্ডে কয়েক হাজার internal call হয়। বাস্তবের message এ field ও থাকে অনেক বেশি। তখন ছোট payload মানে network এ কম byte, আর JSON parse করার চেয়ে কম CPU। gRPC চলে HTTP/2 এর উপর, যেটা একটাই connection দিয়ে একসাথে অনেকগুলো request চালাতে পারে। প্রতিটা call এর জন্য নতুন connection খুলতে হয় না।
| REST + JSON | gRPC + Protobuf | |
|---|---|---|
| মানুষ পড়তে পারে? | হ্যাঁ, text | না, binary |
| Payload এর আকার | বড় (field এর নামসহ যায়) | ছোট (শুধু নম্বর আর মান) |
| Contract | আলাদা document লাগে (যেমন OpenAPI) | .proto file নিজেই contract |
| Browser থেকে সরাসরি | সহজ | বাড়তি layer লাগে |
| যেখানে বেশি মানায় | বাইরের client (mobile app, browser), public API | ভেতরের service থেকে service, যেখানে call অনেক বেশি |
অনেক company দুটোই ব্যবহার করে। বাইরের দুনিয়ার সাথে কথা হয় REST আর JSON এ, কারণ সবাই সেটা বোঝে আর debug করা সহজ। ভেতরের service গুলো নিজেদের মধ্যে কথা বলে gRPC তে, কারণ সেখানে call অনেক বেশি আর গতি বেশি জরুরি। কোনটা “ভালো”, সেটা বড় প্রশ্ন নয়। দুই পক্ষ একই নিয়মে কথা বলছে কিনা, সেটাই আসল। সেই নিয়মের নাম contract।
০৩ API contract কী, আর API versioning ছাড়া একটা field বদলালে কী ভাঙে?
Contract: দুই service এর মধ্যে চুক্তি
Pricing আর Summary আলাদা দুইটা team চালায়। দুইজনের code আলাদা, আর deploy ও হয় আলাদা দিনে। তাদের মধ্যে একটা লিখিত চুক্তি থাকে: কোন endpoint এ request যাবে, request এ কী থাকবে, উত্তরে কোন field আসবে, প্রতিটা field এর type আর মানে কী, আর error হলে কী আসবে। এই চুক্তির নাম API contract।
মানে কথাটা খেয়াল করুন। "fare": 285 বললে ২৮৫ টাকা নাকি ২৮৫ পয়সা, সেটাও contract এর অংশ। gRPC তে .proto file টাই লিখিত contract। REST এ সাধারণত একটা OpenAPI file এই কাজ করে।
একটা মঙ্গলবারের ছোট বদল
Pricing team ঠিক করলো, ভাড়া এখন থেকে পয়সায় হিসাব হবে, যাতে ৳২৮৫.৫০ এর মতো ভাড়াও ঠিকঠাক দেখানো যায়। একজন developer উত্তরের fare field এর নাম বদলে fare_paisa করে দিলো, আর মঙ্গলবার দুপুরে deploy করলো। Summary team আগে থেকেই জানতো, তারা তৈরি ছিলো। কিন্তু Booking service ও Pricing কে ডাকে, আর Booking team এই খবর পায়নি। Booking এর code তখনো fare খুঁজছে। Field টা আর না থাকায় সে ধরে নিলো ভাড়া ০। সেই বিকেলে যারা ride book করলো, তারা estimate এ দেখলো ৳0।
এমন যেকোনো বদল, যেটা পুরোনো client কে ভেঙে দেয়, তাকে বলে breaking change। কোন বদল নিরাপদ আর কোনটা নয়, সেটা জানা থাকলে বেশিরভাগ দুর্ঘটনা এড়ানো যায়:
| বদল | পুরোনো client এর কী হয় |
|---|---|
| নতুন একটা field যোগ করা | কিছুই হয় না। অচেনা field সে উপেক্ষা করে। নিরাপদ। |
| Field এর নাম বদলানো | পুরোনো নামে খুঁজে কিছু পায় না। ভাঙে। |
| Field মুছে ফেলা | একই কথা। ভাঙে। |
| Type বা মানে বদলানো (taka থেকে paisa) | সবচেয়ে বিপজ্জনক। Field এর নাম একই থাকলে কোনো error আসে না, কিন্তু client ২৮৫০০ কে টাকা ভেবে দেখায় ৳২৮,৫০০। নীরবে ভাঙে। |
API versioning: পুরোনোটা রেখে নতুনটা পাশে চালু করা
Breaking change একেবারে না করে থাকা যায় না, ব্যবসার প্রয়োজন বদলায়। তাই নিয়ম হলো: পুরোনো contract ভাঙবেন না, নতুন একটা version পাশে চালু করবেন। একই service এর একাধিক contract একসাথে চালু রাখার এই কৌশলকে বলে API versioning।
ধাপগুলো সাধারণত এমন:
- Pricing নতুন shape টা
/v2/fareএ চালু করে।/v1/fareআগের মতোই চলে। - প্রতিটা client team নিজের সুবিধামতো সময়ে v2 তে সরে যায়।
- Pricing team মাপতে থাকে, v1 এ এখনো কারা request পাঠাচ্ছে।
- v1 এর traffic শূন্যে নামলে তবেই v1 বন্ধ করা হয়।
Mobile app এর ক্ষেত্রে এই অপেক্ষা অনেক লম্বা হয়। অনেকে মাসের পর মাস app update করে না, তাই public API এর পুরোনো version কয়েক বছরও চালু রাখতে হয়।
Protobuf এ একটা বাড়তি সুবিধা আছে। Wire এ field এর নাম যায় না, যায় শুধু নম্বর। তাই .proto file এ field এর নাম বদলালেও পুরোনো client ভাঙে না। কিন্তু নম্বর বা type বদলালে ভাঙে। তাই Protobuf এর নিয়ম: একবার কোনো নম্বর ব্যবহার হলে সেটা আর কখনো অন্য field কে দেবেন না।
আপনার API কে যে ডাকে, সে আপনার উপর ভরসা করে নিজের code লিখেছে। তাই contract বদলানোর আগে ভাবুন, কে কে এই field পড়ে। নতুন জিনিস যোগ করুন, কিন্তু পুরোনো জিনিস জায়গামতো রেখে দিন। মানে বা type বদলাতে হলে নতুন version খুলুন।
০৪ Call গুলো series এ চললে latency chain কত লম্বা হয়?
সাকিব যখন Summary service প্রথম লিখেছিলো, তখন সে call গুলো একটার পর একটা করেছিলো। প্রথমে Trip, তারপর Driver, তারপর Pricing, এভাবে শেষে Promo। Code পড়তে সহজ, আর ঠিকঠাক কাজও করছিলো।
কিন্তু এভাবে একটার পর একটা call করলে প্রতিটা call এর সময় যোগ হয়ে যায়। একে বলে latency chain। শিকলের প্রতিটা আংটা মোট দৈর্ঘ্যে নিজের অংশ যোগ করে। প্রথম সারিতে দেখুন:
ছয়টা call এর সময় যোগ করলে ৩০ + ৪০ + ৮০ + ৫০ + ৬০ + ৪০, মানে ৩০০ms। এর সাথে যোগ হয় Summary এর নিজের কাজ, আর rider এর mobile network এর আসা-যাওয়া। ফলে rider প্রায় আধা সেকেন্ড পর screen দেখে।
শুধু সময় নয়, failure ও যোগ হয়
Chain লম্বা হলে শুধু সময় বাড়ে না। প্রতিটা service কখনো কখনো fail করে। ধরুন প্রতিটা service ৯৯.৯% সময় ঠিকঠাক উত্তর দেয় (SLA আর availability এর ধারণা সেশন ০১ এ দেখেছেন)। Screen টা ঠিকঠাক দেখাতে ছয়টা service কেই একসাথে ঠিক থাকতে হবে:
| Chain এ কয়টা service | সবাই একসাথে ঠিক থাকার সম্ভাবনা | মাসে মোটামুটি কতক্ষণ screen ভাঙা |
|---|---|---|
| ১টা | ৯৯.৯% | প্রায় ৪৩ মিনিট |
| ৬টা | ০.৯৯৯ এর ৬ ঘাত, প্রায় ৯৯.৪% | প্রায় ৪.৩ ঘণ্টা |
| ২০টা | প্রায় ৯৮% | প্রায় ১৪ ঘণ্টা |
প্রতিটা service আলাদাভাবে খুব ভালো। তবু chain এ জুড়লে পুরো screen এর অবস্থা অনেক খারাপ দেখায়। Chain এর গভীরতাও গুনতে হয়। Pricing নিজেও surge হিসাবের জন্য আরো দুইটা service ডাকলে সেগুলোও এই chain এর অংশ হয়ে যায়।
Series এ সাজানো call এর সময় যোগ হয়, আর ঠিক থাকার সম্ভাবনা গুণ হয়। তাই একটা request এর পথে যত কম service থাকে, তত ভালো। নতুন service যোগ করার আগে জিজ্ঞেস করুন, এটা কি সত্যিই এই request এর পথেই থাকতে হবে?
০৫ Call গুলো parallel এ fan-out করলে কতটা লাভ, আর tail latency কেন ভোগায়?
আগে দেখুন, কে কার উপর নির্ভর করে
সাকিব code টা আবার পড়লো। Trip service প্রথমে লাগে, কারণ trip থেকেই driver এর id, পথের তথ্য আর payment এর id আসে। কিন্তু Trip এর উত্তর হাতে এলে বাকি পাঁচটা call কেউ কারো উপর নির্ভর করে না। Driver এর ছবি জানতে ভাড়ার দরকার নেই। Promo জানতে map এর দরকার নেই।
তাই সে বাকি পাঁচটা call একসাথে পাঠালো, আর সবগুলোর উত্তরের জন্য একসাথে অপেক্ষা করলো। একটা request থেকে এভাবে অনেকগুলো call একসাথে ছড়িয়ে দেওয়াকে বলে fan-out। উপরের diagram এর দ্বিতীয় সারিতে দেখুন। মোট সময় এখন Trip এর ৩০ms আর সবচেয়ে ধীর Pricing এর ৮০ms, মানে ১১০ms। ৩০০ms থেকে ১১০ms, শুধু call গুলোর ক্রম বদলে।
Parallel এ মোট সময় হয় সবচেয়ে ধীর call টার সমান। বাকিরা আগে শেষ হলেও Summary কে সবচেয়ে ধীরজনের জন্য অপেক্ষা করতেই হয়। এই কথাটাই একটা নতুন সমস্যা তৈরি করে।
Tail latency: দলের সবচেয়ে ধীরজন
একটা service এর গড় সময় দিয়ে তার আসল চেহারা বোঝা যায় না। তাই engineer রা p99 মাপেন। ১০০টা request কে দ্রুত থেকে ধীর ক্রমে সাজালে ৯৯তম request টা যত সময় নেয়, সেটাই p99। মানে ৯৯% request এর চেয়ে দ্রুত শেষ হয়, আর ১% এর চেয়ে বেশি সময় নেয়। এই ধীর ১% কে বলে tail latency।
একটা service এর ১% ধীর request শুনতে ছোট সমস্যা। কিন্তু fan-out এ একটা screen অনেকগুলো service এর উপর একসাথে নির্ভর করে:
ধরুন পাঁচটা service এর প্রতিটা: ৯৯% call : 100ms এর মধ্যে উত্তর দেয় ১% call : 1 second লাগে (GC pause, ঠান্ডা cache, ধীর disk) Parallel এ পাঁচটা call গেলে screen দ্রুত হবে শুধু তখন, যখন পাঁচটাই দ্রুত: P(পাঁচটাই দ্রুত) = 0.99 ^ 5 = 0.951 P(অন্তত একটা ধীর) = 1 - 0.951 = 0.049 (প্রায় ২০টা screen এ ১টা) সন্ধ্যার peak এ 400 screen/second: 400 × 0.049 ≈ 20 জন rider প্রতি সেকেন্ডে পুরো 1 second অপেক্ষা করে
প্রতিটা service আলাদাভাবে ৯৯% সময় দ্রুত। তবু প্রতি ২০টা screen এর মধ্যে একটা ধীর হয়। Fan-out যত চওড়া হয়, সমস্যা তত বাড়ে। একটা request ১০০টা service ছুঁয়ে গেলে প্রায় ৬৩% request এ অন্তত একটা ধীর উত্তর থাকে। Google এর engineer রা এই সমস্যা নিয়ে “The Tail at Scale” নামে একটা বিখ্যাত লেখা লিখেছিলেন। বড় system এ গড় নয়, tail ই user এর অভিজ্ঞতা ঠিক করে দেয়।
Parallel call মোট সময় অনেক কমায়, তাই নির্ভরতা না থাকলে call গুলো একসাথে পাঠান। কিন্তু মনে রাখুন, তখন সবচেয়ে ধীর service টাই আপনার গতি ঠিক করে। কোনো একটা service অস্বাভাবিক ধীর হলে কী হবে, সেই প্রশ্নের উত্তর আগেই ঠিক করে রাখতে হয়। পরের দুইটা section সেটা নিয়েই।
০৬ একটা service ধীর হলে পুরো app কেন আটকে যায়?
এবার শুক্রবার সন্ধ্যায় ফেরা যাক। ঠিক কী হয়েছিলো, সাকিব পরে logs ঘেঁটে বের করলো।
Partial failure: কিছু অংশ ঠিক, কিছু অংশ নয়
Monolith এ সাধারণত দুইটা অবস্থা: পুরো program চলছে, অথবা পুরোটা crash করেছে। অনেকগুলো service এর system এ একটা তৃতীয় অবস্থা খুব সাধারণ: কিছু service ঠিক আছে, আর কিছু ধীর বা down। একে বলে partial failure। অনেক machine এ ছড়ানো এমন system কে বলে distributed system। সেখানে partial failure ব্যতিক্রম নয়, প্রতিদিনের ঘটনা।
সেদিন সন্ধ্যা ছয়টা দুই মিনিটে Pricing এর surge হিসাবের একটা database query হঠাৎ ধীর হয়ে গেলো। Table টায় নতুন একটা column এ খোঁজা হচ্ছিলো, কিন্তু সেটার index ছিলো না (index এর কথা সেশন ০৭ এ দেখেছেন)। প্রতিটা fare এর উত্তর আসতে এখন ৮০ms এর বদলে ৫ সেকেন্ড লাগছে। Pricing কিন্তু down হয়নি। সে উত্তর দিচ্ছে, শুধু অনেক দেরিতে।
আটকে থাকা thread এর হিসাব
সেশন ০৮ এ দেখেছেন, server এর ভেতরে thread গুলো কর্মীর মতো, প্রত্যেকে একবারে একটা request সামলায়। Summary service এর আছে ২০০টা thread। একটা thread Pricing কে call করে উত্তরের অপেক্ষায় থাকলে সেই পুরো সময় সে আর কারো কাজ করতে পারে না। কয়টা thread একসাথে আটকে থাকবে, সেটা একটা সহজ সূত্রে বের করা যায়:
একসাথে আটকে থাকা thread = প্রতি সেকেন্ডে request × প্রতিটা request এর সময়
(Little's Law)
Summary Service এর আছে 200টা thread।
স্বাভাবিক সন্ধ্যা : 400 req/s × 0.11 s = 44 thread busy (200 এর মধ্যে, আরামে চলে)
Pricing ধীর (5s) : 400 req/s × 5 s = 2000 thread দরকার (আছে মাত্র 200)
200টা thread ভরে যায়: 200 ÷ 400 = মাত্র 0.5 second এ
তারপর নতুন সব request লাইনে দাঁড়ায়, যেগুলোর Pricing লাগেই না সেগুলোও
এই সূত্রের নাম Little’s Law। এটা মনে রাখলে system design interview তে অনেক প্রশ্নের হিসাব মুখে মুখে করা যায়। স্বাভাবিক দিনে Summary এর ২০০ জনের মধ্যে মাত্র ৪৪ জন ব্যস্ত থাকে। Pricing ধীর হতেই দরকার হয় ২০০০ জন। আধা সেকেন্ডের মধ্যে সব thread Pricing এর অপেক্ষায় আটকে যায়।
Cascading failure: ধীরতা উপরের দিকে ছড়ায়
এরপর যা হলো, সেটাই আসল বিপদ:
Summary এর সব thread আটকে যাওয়ায় সে trip history এর মতো এমন request ও নিতে পারছিলো না, যেগুলোর Pricing লাগেই না। Booking service ও ভাড়ার estimate এর জন্য Pricing কে ডাকে। তার thread গুলোও একইভাবে আটকে গেলো, আর নতুন ride book হওয়া বন্ধ হলো। Rider রা spinner দেখে বারবার refresh করছিলো, ফলে request আরো বাড়লো।
একটা service এর সমস্যা যখন তার caller দের আটকে দেয়, আর তারা আবার নিজেদের caller দের আটকায়, তখন তাকে বলে cascading failure। সিঁড়িতে একজন হোঁচট খেলে যেমন পেছনের সবাই একে একে আটকে যায়, তেমন। সাকিবের ঘটনায় একটা index এর অভাবে পুরো app বন্ধ হয়ে গিয়েছিলো।
Pricing পুরোপুরি down হলে connection সাথে সাথে fail করতো। Summary কয়েক millisecond এর মধ্যে error পেয়ে পরের কাজে চলে যেতো। ধীর service error দেয় না, শুধু অপেক্ষা করায়। সেই অপেক্ষাতেই caller এর thread, memory আর connection আটকে যায়। তাই “উত্তর আসছে না” অবস্থাটাকে সবচেয়ে বেশি ভয় করুন।
Cascading failure ঠেকানোর পুরো কৌশল (circuit breaker, bulkhead, rate limiting) আমরা দেখবো সেশন ২১ এ। কিন্তু প্রথম আর সবচেয়ে জরুরি প্রতিরক্ষা খুব সহজ, আর সেটা এখনই শিখে নেওয়া যায়।
০৭ প্রতিটা call এ timeout কেন লাগে, আর মানটা কত হবে?
সেশন ০১ এ দেখেছেন, timeout মানে একটা নির্দিষ্ট সময়সীমা। তার মধ্যে উত্তর না এলে অপেক্ষা ছেড়ে দেওয়া হয়। সাকিব খুঁজে দেখলো, Summary এর Pricing call এ কোনো timeout ই দেওয়া ছিলো না। অনেক HTTP library তে default timeout থাকে না, অথবা থাকে অনেক লম্বা। যেমন Python এর জনপ্রিয় requests library তে timeout না দিলে সে উত্তরের জন্য অনির্দিষ্ট সময় অপেক্ষা করতে পারে।
সাকিব Pricing call এ ৩০০ms এর timeout বসালো। আর ঠিক করলো, সময় পার হলে কী দেখাবে:
এখন Pricing ৫ সেকেন্ড নিলেও Summary এর একটা thread বড়জোর ৩০০ms আটকে থাকে। Little’s Law দিয়ে আবার হিসাব করুন: ৪০০ × ০.৩, মানে ১২০টা thread। ২০০ এর মধ্যেই থাকে। Summary চালু থাকে, আর তার সাথে চালু থাকে বাকি app।
Fallback: পুরোটা না পারলে যতটা পারা যায়
উত্তর না এলে কী দেখাবেন, সেই বিকল্প ব্যবস্থার নাম fallback। সাকিবের ক্ষেত্রে fallback হলো ভাড়ার জায়গায় “একটু পরে দেখাচ্ছি” লেখা। আরেকটা উপায় হতে পারে cache এ রাখা শেষ জানা ভাড়াটা দেখানো (cache এর কথা সেশন ০৪ এ)। এভাবে একটা অংশ fail করলেও পুরো screen না ভেঙে কম তথ্য নিয়ে চালিয়ে যাওয়াকে বলে graceful degradation। Rider এর কাছে “ভাড়া একটু পরে আসবে” অনেক ভালো, পুরো app আটকে থাকার চেয়ে।
Timeout এর মান কত হবে
Timeout খুব ছোট হলে স্বাভাবিক call ও fail করবে। খুব বড় হলে thread আটকে থাকবে। একটা ভালো শুরু হলো: যে service কে ডাকছেন, তার p99 দেখুন, আর তার একটু উপরে timeout দিন। Pricing এর স্বাভাবিক p99 ছিলো ২০০ms, তাই সাকিব দিলো ৩০০ms।
Timeout budget: পুরো পথের জন্য একটা সময়
Timeout শুধু একটা call এর ব্যাপার নয়। পুরো request এর জন্য একটা মোট সময় থাকে, আর ভেতরের প্রতিটা call কে সেই সময়ের ভেতরে থাকতে হয়। একে বলে timeout budget। সাকিবের হিসাবটা দেখুন:
| কে | কতক্ষণ অপেক্ষা করে |
|---|---|
| Rider App, Summary এর জন্য | ২০০০ms |
| Summary, নিজের পুরো কাজের জন্য | ১০০০ms এর মধ্যে শেষ করতে চায় |
| Summary, Trip call এর জন্য (series) | ১৫০ms |
| Summary, বাকি পাঁচটা parallel call এর জন্য | প্রতিটা ৩০০ms |
| সবচেয়ে খারাপ ক্ষেত্রে Summary এর মোট অপেক্ষা | ১৫০ + ৩০০, মানে ৪৫০ms, budget এর ভেতরে |
ভেতরের timeout সবসময় বাইরের timeout এর চেয়ে ছোট রাখতে হয়। উল্টো হলে app অপেক্ষা ছেড়ে দেওয়ার পরও Summary কাজ করে যাবে, অথচ সেই উত্তর আর কেউ নেবে না। gRPC তে এর জন্য একটা সুবিধা আছে: caller এর হাতে কত সময় বাকি, সেটা (deadline) call এর সাথেই পরের service এ চলে যায়। সময় ফুরিয়ে গেলে পরের service কাজটা আর শুরুই করে না।
Network এর উপর দিয়ে যাওয়া প্রতিটা call এ timeout থাকবে, কোনো ব্যতিক্রম নেই। আর প্রতিটা timeout এর সাথে ঠিক করা থাকবে, সময় পার হলে user কী দেখবে।
০৮ Retry কখন নিরাপদ?
Timeout হলো, এখন কী করবেন? প্রথম চিন্তা আসে: আরেকবার চেষ্টা করি। অনেক সময় এটা ঠিকও। Network এ একটা packet হারিয়ে গিয়েছিলো, বা Pricing এর একটা copy ঠিক তখন restart হচ্ছিলো। এমন ক্ষণিকের সমস্যাকে বলে transient failure। দ্বিতীয়বার চেষ্টায় এগুলো প্রায়ই ঠিক হয়ে যায়। কিন্তু retry এর দুইটা লুকানো বিপদ আছে।
বিপদ ১: retry গুণ হয়ে যায়
সাকিবের system এ retry ছিলো সব জায়গায়। Rider App fail হলে তিনবার চেষ্টা করে। Summary ও Pricing call fail হলে তিনবার চেষ্টা করে। প্রত্যেকে নিজের দিক থেকে ঠিক কাজই করছে। কিন্তু সব মিলিয়ে হিসাবটা দাঁড়ায় এমন:
Pricing যখন এমনিতেই ধীর, তখন retry গুলো তার উপর আরো চাপ বাড়ায়। ধীর service আরো ধীর হয়, আরো timeout হয়, আরো retry আসে। এই চক্রকে বলে retry storm। শুক্রবার সন্ধ্যায় Pricing এর কাছে আসলে স্বাভাবিকের চেয়ে কয়েক গুণ বেশি request এসেছিলো, যার বেশিরভাগই retry।
বিপদ ২: কাজটা হয়তো আগেই হয়ে গেছে
Timeout হলে caller জানে না, অন্য পাশে কাজটা হয়েছে কিনা। ভাড়া জানতে চাওয়ার মতো call এ এতে সমস্যা নেই। দুইবার জিজ্ঞেস করলেও উত্তর একই। কিন্তু টাকা কাটার call এ ব্যাপারটা আলাদা:
সমাধান আপনি সেশন ০৮ এ দেখেছেন: idempotency। প্রতিটা charge request এর সাথে একটা unique key পাঠানো হয়, এখানে trip এর id। Payment service আগে দেখে নেয়, এই key এর কাজ আগে হয়েছে কিনা। হয়ে থাকলে আবার টাকা না কেটে আগের ফলাফলটাই ফেরত দেয়।
নিরাপদ retry এর নিয়ম
| Call | Retry করা যায়? |
|---|---|
| ভাড়া জানা, driver এর তথ্য পড়া (শুধু পড়া) | হ্যাঁ, এমনিতেই idempotent |
| Rating এর মান বসানো (“rating = 5”) | হ্যাঁ, বারবার বসালেও একই ফল |
| টাকা কাটা, ride book করা, SMS পাঠানো | শুধু idempotency key থাকলে |
| Driver এর আয়ে ৳285 “যোগ করা” | Key ছাড়া কখনো না |
এর সাথে আরো কয়েকটা নিয়ম মানতে হয়:
- শুধু এক layer এ retry করুন: সাধারণত যে service সমস্যার সবচেয়ে কাছে, সেখানে। বাকিরা retry না করে error সোজা উপরে পাঠায়।
- চেষ্টার সংখ্যা ছোট রাখুন: একবার বা দুইবার। তিনবারেও না হলে চতুর্থবারেও সম্ভবত হবে না।
- মাঝে অপেক্ষা করুন, আর অপেক্ষা বাড়াতে থাকুন: প্রতিবার আগের চেয়ে বেশি অপেক্ষা (exponential backoff), আর তার সাথে একটু random সময় যোগ (jitter), যাতে সবাই একই মুহূর্তে আবার চেষ্টা না করে। দুইটাই সেশন ০৯ এ বিস্তারিত আছে।
- Retry এর একটা বাজেট রাখুন: যেমন, মোট request এর ১০% এর বেশি retry হবে না। বাজেট ফুরালে retry বন্ধ। তখন বোঝা যায়, সমস্যাটা ক্ষণিকের নয়।
ক্ষণিকের সমস্যায় retry দারুণ কাজ করে। কিন্তু ধীর বা overloaded service এর উপর বেপরোয়া retry আগুনে ঘি ঢালার মতো। Retry দিন শুধু এক জায়গায়, সংখ্যা ছোট রেখে, মাঝে অপেক্ষা করে। আর যে call দুইবার চললে ক্ষতি হয়, সেখানে idempotency key ছাড়া retry দেবেন না।
০৯ সব service এ একই নিয়ম কীভাবে চালু রাখবেন?
শুক্রবারের ঘটনার পর সাকিবের company ঠিক করলো, সব service এ timeout, retry আর idempotency এর নিয়ম মানতে হবে। কাজটা শুনতে সহজ, করতে কঠিন। Company তে এখন ৪০টা service। কোনোটা Go তে লেখা, কোনোটা Java তে, কোনোটা Node.js এ। প্রতিটা team নিজের ভাষার library দিয়ে timeout আর retry লেখে, নিজের মতো করে। কেউ ভুলে যায়, কেউ ভুল মান দেয়। তার উপর security team চায়, ভেতরের সব call encrypted হোক। প্রতিটা team কে আলাদা করে সেটাও করতে হবে।
Sidecar: প্রতিটা service এর পাশে একজন সহকারী
এর একটা সমাধান হলো network এর এই সব কাজ service এর code থেকে বের করে আনা। প্রতিটা service এর পাশে, একই machine এ, একটা ছোট আলাদা program চালানো হয়। Service এর সব আসা-যাওয়া call এই program এর ভেতর দিয়ে যায়। মোটরসাইকেলের পাশে জোড়া লাগানো ছোট বসার গাড়িটাকে ইংরেজিতে sidecar বলে। এই program টাও service এর পাশে জোড়া লাগানো থাকে, তাই এর নাম sidecar। Sidecar আসলে একটা proxy, মানে যে নিজে request টা নিয়ে আসল গন্তব্যে পৌঁছে দেয়।
Summary এখন শুধু নিজের business logic লেখে। Pricing কে ডাকলে call টা আগে যায় পাশের sidecar এ। Sidecar timeout দেয়, দরকার হলে retry করে, call টা encrypted করে, আর কত সময় লাগলো তার হিসাব রাখে।
Service mesh: সব sidecar মিলে একটা জাল
সব service এর পাশের sidecar গুলো মিলে একটা জালের মতো তৈরি হয়, যার ভেতর দিয়ে সব internal call যায়। এই পুরো ব্যবস্থাকে বলে service mesh। এর দুইটা অংশ:
- Data plane: সব sidecar মিলে। আসল traffic এদের ভেতর দিয়েই যায়।
- Control plane: একটা কেন্দ্রীয় system, যে ঠিক করে দেয় কোন call এ কত timeout, কতবার retry। নিয়ম একবার বদলালে সেটা সব sidecar এ পৌঁছে যায়, কোনো service এর code বদলাতে হয় না।
Diagram এ “pod” শব্দটা দেখছেন। Pod হলো Kubernetes এর সবচেয়ে ছোট চলমান একক, যেখানে এক বা একাধিক program একসাথে, একই machine এ চলে। Service আর তার sidecar একই pod এ থাকে। দুই sidecar এর মধ্যে call যায় mTLS (mutual TLS) দিয়ে। এতে দুই পক্ষই certificate দেখিয়ে নিজের পরিচয় প্রমাণ করে, আর মাঝের data encrypted থাকে।
প্রচলিত service mesh এর নাম Istio আর Linkerd। অনেক mesh এ sidecar হিসেবে Envoy নামের proxy ব্যবহার হয়। Mesh এর একটা বাড়তি লাভ হলো, সব call একই জায়গা দিয়ে যায় বলে কোন service কাকে কতবার ডাকছে আর কত সময় লাগছে, তার পুরো ছবি এক জায়গায় পাওয়া যায়।
Downside গুলো
- প্রতিটা call এ দুইটা বাড়তি hop: নিজের sidecar আর অন্য পাশের sidecar। প্রতি hop এ সাধারণত এক millisecond এরও কম লাগে, তবু শূন্য নয়।
- বাড়তি memory আর CPU: ৪০টা service এর প্রতিটা copy র পাশে একটা করে sidecar চলে।
- নতুন জটিলতা: Mesh নিজেই একটা বড় system। চালাতে, বুঝতে আর debug করতে আলাদা দক্ষতা লাগে।
তাই পাঁচ-ছয়টা service এর ছোট system এ mesh সাধারণত দরকার হয় না। একটা ভালো shared library দিয়েই কাজ চলে যায়। Service এর সংখ্যা আর ভাষা বাড়লে mesh এর লাভ cost কে ছাড়িয়ে যায়। কোন service কোন address এ আছে, সেটা খুঁজে বের করার কাজটাও (service discovery) প্রায়ই mesh করে। সেটা আমরা দেখবো সেশন ১৪ এ।
পরের শুক্রবার
এক সপ্তাহ পর, আবার শুক্রবার সন্ধ্যা। সাকিবের দল ততদিনে Summary এর call গুলো parallel করেছে। প্রতিটা call এ timeout দিয়েছে। Retry শুধু এক layer এ রেখেছে, আর payment এ idempotency key বসিয়েছে। সাতটার দিকে Pricing আবার দুই মিনিটের জন্য ধীর হলো। এবার কিছু rider শুধু দেখলো “ভাড়া একটু পরে দেখাচ্ছি”। Map এলো, driver এর ছবি এলো, ride book হলো। Support team এর group চুপচাপ থাকলো। সাকিব নিশ্চিন্তে একটা bike ডেকে বাড়ি ফিরলো।
Service গুলোর মধ্যে প্রতিটা call একটা network call, function call নয়। এটা ধীর হতে পারে, হারাতে পারে, অর্ধেক কাজ করে থামতে পারে। তাই প্রতিটা call এ চারটা প্রশ্নের উত্তর আগে থেকে ঠিক করে রাখুন: কতক্ষণ অপেক্ষা করবো (timeout), উত্তর না এলে কী দেখাবো (fallback), আবার চেষ্টা করা নিরাপদ কিনা (idempotency), আর কে retry করবে (এক layer)।
১০ মগজে প্রেশার দিন
আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“১ms খুবই কম। দশটা call করলেও মাত্র ১০ms। তাই গতির দিক থেকে কোনো সমস্যা নেই, function call এর মতো ধরে নিলেই চলে।”
গড় গতির হিসাবটা ঠিক আছে, কিন্তু আসল বিপদটা গতিতে নয়।
যে জায়গাটা যোগ করা দরকার
Function call আর network call এর পার্থক্য গড় সময়ে নয়, ব্যর্থ হওয়ার ধরনে। Network call কখনো কখনো ৫ সেকেন্ড নেয় (tail latency)। কখনো একেবারেই উত্তর আসে না। আবার কখনো অন্য পাশে কাজ হয়ে যায়, কিন্তু উত্তরটা পথে হারায়। Function call এ এর কোনোটাই হয় না। তাই প্রতিটা remote call এ timeout, fallback আর নিরাপদ retry এর ব্যবস্থা লাগে।
Network call কে function call এর মতো দেখালেও তার ফল তিন রকম: সফল, ব্যর্থ, আর “জানি না”। ভালো design শুরু হয় এই তৃতীয় অবস্থাটা মেনে নিয়ে।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“Down হওয়াটাই বেশি খারাপ। ধীর হলেও অন্তত উত্তর তো আসছে, ভাড়াটা শেষ পর্যন্ত দেখানো যাচ্ছে।”
একটা request এর দিক থেকে কথাটা ঠিক। কিন্তু পুরো system এর দিক থেকে দেখলে ছবিটা উল্টে যায়।
যে জায়গাটা যোগ করা দরকার
Down service সাথে সাথে error দেয়। Caller এর thread কয়েক millisecond এ মুক্ত হয়, আর সে fallback দেখিয়ে পরের কাজে যায়। ধীর service caller এর thread আটকে রাখে। Little’s Law দিয়ে হিসাব করুন: প্রতি সেকেন্ডে ৪০০ request আর ৫ সেকেন্ড অপেক্ষা মানে ২০০০টা thread একসাথে আটকে থাকা। Caller এর thread ফুরিয়ে যায়, আর সমস্যা উপরে ছড়ায়: cascading failure। Timeout না থাকলে ধীর service আসলে down service এর চেয়েও খারাপ।
দ্রুত fail করা একটা সুবিধা। Timeout দিয়ে একটা ধীর service কে caller এর চোখে “দ্রুত fail করা” service এ বদলে দিন।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“Retry করলে ক্ষণিকের network সমস্যা নিজে থেকেই ঠিক হয়ে যায়। তিনবার চেষ্টা করলে success এর সম্ভাবনা অনেক বাড়ে।”
ক্ষণিকের সমস্যার জন্য কথাটা ঠিক। কিন্তু তিনটা জিনিস বাদ পড়েছে।
যে জায়গাটা যোগ করা দরকার
প্রথমত, প্রতিটা layer তিনবার retry করলে retry গুণ হয়। তিন layer এ নিচের service ২৭ গুণ request পায়, ঠিক যখন সে এমনিতেই ধীর: retry storm। দ্বিতীয়ত, timeout হলে জানা যায় না কাজটা হয়েছে কিনা। টাকা কাটার মতো call এ idempotency key ছাড়া retry করলে দুইবার টাকা কাটা যায়। তৃতীয়ত, বিরতি ছাড়া সাথে সাথে retry করলে হাজারো client একই মুহূর্তে আবার request পাঠায়। তাই retry থাকবে এক layer এ, এক-দুইবার, backoff আর jitter সহ, আর শুধু idempotent call এ।
Retry এর আগে দুইটা প্রশ্ন করুন: আর কে কে এই call টা retry করছে? আর এই call দুইবার চললে কি কোনো ক্ষতি হবে?
১১ যে টার্মগুলো শিখলাম
| টার্ম | মানে |
|---|---|
| RPC (Remote Procedure Call) | অন্য machine এর function কে নিজের function এর মতো করে ডাকা। পেছনে আসলে network request যায়। |
| Sync call | Caller উত্তর না আসা পর্যন্ত অপেক্ষা করে। |
| Async message | Caller message রেখে নিজের কাজে ফেরে, অন্য service পরে কাজটা করে। |
| REST | HTTP method আর URL দিয়ে request, সাধারণত JSON এ উত্তর। |
| gRPC | Google এর তৈরি RPC framework। .proto file থেকে client আর server এর code বানায়, HTTP/2 তে চলে। |
| Protobuf | gRPC এর binary data format। Field এর নামের বদলে ছোট নম্বর পাঠায়, তাই payload ছোট হয়। |
| HTTP/2 | HTTP এর নতুন version, যেটা একটা connection দিয়ে একসাথে অনেক request চালাতে পারে। |
| API contract | দুই service এর চুক্তি: কোন endpoint, কোন field, কোন type, কোন মানে। |
| Breaking change | এমন বদল, যেটা পুরোনো client কে ভেঙে দেয়। যেমন field এর নাম, type বা মানে বদলানো। |
| API versioning | পুরোনো contract চালু রেখে নতুন contract আলাদা version এ (/v1, /v2) পাশাপাশি চালানো। |
| Latency chain | Series এ সাজানো call গুলোর সময় যোগ হয়ে মোট সময় লম্বা হওয়া। |
| Fan-out | একটা request থেকে অনেকগুলো call একসাথে parallel এ পাঠানো। |
| p99 / Tail latency | ১০০টা request এর মধ্যে সবচেয়ে ধীর ১% এর সময়। Fan-out এ এটাই user এর অভিজ্ঞতা ঠিক করে। |
| Partial failure | System এর কিছু অংশ ঠিক, আর কিছু অংশ ধীর বা down থাকা। |
| Cascading failure | একটা service এর সমস্যা তার caller দের আটকে দেয়, তারা আবার তাদের caller দের, এভাবে পুরো system অচল হয়ে যায়। |
| Little’s Law | একসাথে কতগুলো কাজ চলছে, তার হিসাব: প্রতি সেকেন্ডে আসা কাজের সংখ্যাকে প্রতিটা কাজের সময় দিয়ে গুণ করলেই পাওয়া যায়। |
| Fallback / Graceful degradation | উত্তর না এলে বিকল্প কিছু দেখানো, যাতে পুরো screen না ভেঙে কম তথ্য নিয়ে চলে। |
| Timeout budget / Deadline | পুরো request এর জন্য মোট সময়। ভেতরের প্রতিটা call কে এর ভেতরে থাকতে হয়। |
| Retry storm | অনেক layer এর retry গুণ হয়ে ধীর service এর উপর বহুগুণ চাপ তৈরি করা। |
| Sidecar | প্রতিটা service এর পাশে চলা ছোট proxy, যে network এর কাজগুলো (timeout, retry, encryption) সামলায়। |
| Service mesh | সব sidecar (data plane) আর তাদের নিয়ম ঠিক করে দেওয়া control plane মিলে পুরো ব্যবস্থা। |
| mTLS | দুই পক্ষই certificate দিয়ে পরিচয় প্রমাণ করে, আর মাঝের data encrypted থাকে। |
| Istio / Linkerd / Envoy | প্রচলিত service mesh (Istio, Linkerd) আর sidecar proxy (Envoy)। |
১২ শর্ট সামারি
- Service থেকে service এর call দেখতে function call এর মতো হলেও আসলে network call। উত্তর এখনই দরকার হলে sync call (REST বা gRPC) দেওয়া হয়, শুধু জানালেই চললে async message। ভেতরের call এ gRPC আর Protobuf ছোট আর দ্রুত, আর দুই service এর মধ্যে API contract ভাঙতে না চাইলে নতুন version পাশাপাশি চালাতে হয়।
- Series এ সাজানো call এর সময় যোগ হয়, তাই নির্ভরতা না থাকলে call গুলো parallel এ fan-out করা ভালো। তখন সবচেয়ে ধীর call টাই মোট সময় ঠিক করে, আর fan-out যত চওড়া, tail latency তত বেশি ভোগায়।
- একটা ধীর service caller এর thread আটকে দেয়, আর সেই ধীরতা উপরে ছড়িয়ে cascading failure ঘটায়। এর প্রথম প্রতিরক্ষা হলো প্রতিটা call এ timeout আর fallback।
- Retry শুধু এক layer এ, অল্প সংখ্যায়, backoff সহ, আর শুধু idempotent call এ নিরাপদ। অনেক service আর অনেক ভাষার system এ এই নিয়মগুলো একসাথে চালু রাখতে service mesh আর sidecar ব্যবহার হয়।
১৩ মনে রাখুন
- Network call কখনো function call নয়। এর ফল তিন রকম: সফল, ব্যর্থ, আর “জানি না”।
- উত্তর ছাড়া এগোনো না গেলে sync, শুধু জানালেই চললে async। যত কাজ async এ সরানো যায়, ধীরতা তত কম ছড়ায়।
- Contract ভাঙবেন না, নতুন version খুলুন। Field যোগ করা নিরাপদ, নাম, type বা মানে বদলানো নয়।
- Series এ সময় যোগ হয়, parallel এ সবচেয়ে ধীরজন জেতে। নির্ভরতা না থাকলে fan-out করুন, আর tail latency এর জন্য তৈরি থাকুন।
- প্রতিটা call এ timeout, প্রতিটা timeout এর সাথে fallback। ধীর service down service এর চেয়েও বিপজ্জনক।
- Retry এক layer এ, অল্প, অপেক্ষা করে, আর শুধু idempotent call এ। নইলে retry storm আর দুইবার টাকা কাটা।
- অনেক service আর অনেক ভাষা হলে service mesh ভাবুন। ছোট system এ একটা shared library ই যথেষ্ট।