System Design শেখো
শেখো / লো-লেটেন্সি ইঞ্জিনিয়ারিং

Backpressure ও Batching

9 মিনিট Module 11 · Low-Latency Engineering 🔥
এক নজরে
  • Backpressure হলো slow consumer-কে রক্ষা করার কৌশল — producer-কে গতি কমাতে বাধ্য করে যাতে সিস্টেম crash না করে।
  • Batching একসাথে অনেক কাজ প্রসেস করে fixed cost amortize করে throughput বাড়ায়, কিন্তু latency সামান্য বাড়ায়।
  • Bounded queue ও load shedding দিয়ে overload-এর সময় সিস্টেমকে স্থিতিশীল ও predictable রাখা যায়।

সমস্যাটা কী?

ভাবো একটা পাইপলাইন — একদিকে producer দ্রুত data তৈরি করছে, অন্যদিকে consumer সেটা প্রসেস করছে। এখন যদি producer consumer-এর চেয়ে দ্রুত হয়? কাজগুলো জমতে থাকবে একটা queue-তে। প্রথমে কিছুক্ষণ ঠিকঠাক, তারপর queue বড় হতে থাকে, memory খেতে থাকে, এবং একসময় হয় সিস্টেম OutOfMemory-তে crash করে, নয়তো latency এত বেড়ে যায় যে data পৌঁছানোর আগেই অর্থহীন হয়ে যায়।

এটা low-latency সিস্টেমের একটা মৌলিক সমস্যা — গতির অমিল। সমাধানের দুটো গুরুত্বপূর্ণ হাতিয়ার হলো backpressure (consumer যেন producer-কে "আস্তে!" বলতে পারে) এবং batching (কাজ গুচ্ছ করে efficiency বাড়ানো)। এই দুটো একসাথে throughput আর latency-র সূক্ষ্ম ভারসাম্য তৈরি করে।

মূল ধারণা

Backpressure হলো সেই প্রক্রিয়া যেখানে একটি slow consumer fast producer-কে সংকেত দেয় গতি কমাতে (বা থামতে), যাতে সিস্টেম তার ক্ষমতার বাইরে কাজ জমিয়ে নিজেকে ধ্বংস না করে।

মূল কথাটা হলো — কোনো সিস্টেম তার সবচেয়ে ধীর অংশের চেয়ে দ্রুত চলতে পারে না। তুমি যদি জোর করে বেশি কাজ ঢোকাও, সেটা কোথাও না কোথাও জমবে। Backpressure সেই অতিরিক্ত কাজকে "জমতে না দিয়ে" উৎসেই গতি নিয়ন্ত্রণ করে। এটি একটি flow-control mechanism।

পাশাপাশি batching হলো অনেকগুলো ছোট কাজকে একসাথে একটি গুচ্ছ হিসেবে প্রসেস করা, যাতে প্রতিবারের fixed overhead (নেটওয়ার্ক round trip, disk seek, syscall) অনেক item-এর মধ্যে ভাগ হয়ে যায়।

কীভাবে কাজ করে

Bounded queue — backpressure-এর ভিত্তি

Backpressure প্রয়োগের সবচেয়ে সহজ উপায় হলো bounded queue — একটি নির্দিষ্ট আকারের queue। queue ভরে গেলে কী হবে, তার কয়েকটি নীতি:

নীতিআচরণকখন উপযুক্ত
Blockproducer অপেক্ষা করে queue খালি হওয়া পর্যন্তdata হারানো যাবে না
Drop newestনতুন item ফেলে দেয়সর্বশেষ data কম গুরুত্বপূর্ণ
Drop oldestপুরোনো item ফেলে নতুন রাখেশুধু সাম্প্রতিক data দরকার (যেমন live price)
Reject/fail-fastcaller-কে error দেয়caller retry/সিদ্ধান্ত নিতে পারে

এর বিপরীতে unbounded queue হলো একটা ফাঁদ — মনে হয় কিছুই হারাচ্ছে না, কিন্তু আসলে memory growth আর latency lurking করছে, এবং সমস্যা ধরা পড়ে দেরিতে, production-এ।

Batching — cost amortization

ধরো ডাটাবেসে একটা row insert করতে network round trip লাগে ১ms। ১০০০টি row একে একে insert করলে = ১০০০ms। কিন্তু একটা batch-এ ১০০০ row একসাথে পাঠালে হয়তো ৫ms — কারণ একটাই round trip। এই হলো batching-এর জাদু — fixed cost amortize হয়।

১০০০টি আলাদা insert: 1000 × 1ms = 1000ms
batch insert (১টি round trip): ~5ms

কিন্তু একটা trade-off আছে। batch পূর্ণ হওয়ার অপেক্ষায় প্রথম item-কে কিছুটা অপেক্ষা করতে হয়। তাই throughput বাড়ে কিন্তু individual latency সামান্য বাড়ে। এটি নিয়ন্ত্রণে রাখতে দুটো শর্ত ব্যবহার করা হয়: "batch ভরে গেলে পাঠাও, অথবা সর্বোচ্চ X মিলিসেকেন্ড অপেক্ষার পর পাঠাও" (time-based flush) — যাতে কম traffic-এ latency অযথা না বাড়ে।

সহজ উদাহরণ

ভাবো ঢাকার একটা বাস। যদি প্রতিটি যাত্রীর জন্য আলাদা বাস ছাড়ে (no batching), প্রতিটি ট্রিপের জ্বালানি ও ড্রাইভারের খরচ একজনের ওপর পড়ে — অপচয়। তাই বাস কিছু যাত্রী জমিয়ে (batch) একসাথে নেয়, খরচ ভাগ হয়ে যায় (throughput বাড়ে)। কিন্তু প্রথম যাত্রীকে বাস ভরা পর্যন্ত একটু অপেক্ষা করতে হয় (latency বাড়ে)। আর backpressure হলো — বাস ভরে গেলে কন্ডাক্টর হাত তুলে "আর জায়গা নাই!" বলে, নতুন যাত্রী ঠেলে ওঠানো হয় না; নাহলে বাস ভেঙে পড়বে।

কৌশল

  • Bounded queue সব async boundary-তে রাখো — কখনো unbounded নয় production-এ।
  • Reactive Streams / backpressure-aware framework ব্যবহার করো (যেমন Project Reactor, RxJava, gRPC flow control, Kafka consumer pull model)।
  • Adaptive batching — load বুঝে batch size ছোট-বড় করো; কম traffic-এ ছোট batch (কম latency), বেশি traffic-এ বড় batch (বেশি throughput)।
  • Time + size dual trigger — batch flush করো size বা timeout, যেটা আগে আসে।
  • Load shedding — overload-এ কম-গুরুত্বপূর্ণ request ইচ্ছাকৃতভাবে drop/fast-fail করো, যাতে গুরুত্বপূর্ণগুলো বাঁচে।

কখন ব্যবহার করবে / করবে না

Backpressure প্রায় সবসময় দরকার — যেকোনো জায়গায় যেখানে producer ও consumer-এর গতি ভিন্ন হতে পারে। Batching ব্যবহার করো যখন per-operation fixed overhead বেশি (DB write, network call, disk flush) এবং সামান্য latency বাড়ানো গ্রহণযোগ্য।

Batching এড়াও যখন প্রতিটি item-এর latency অতি critical এবং কোনো অপেক্ষা সহ্য হবে না — যেমন একটা single order-এর জন্য microsecond-critical path। সেখানে batch জমানোর অপেক্ষাই ক্ষতিকর।

সাবধান

কখনো production-এ unbounded queue রাখবে না — এটা latency bug-কে memory bug-এ রূপান্তর করে এবং crash না হওয়া পর্যন্ত লুকিয়ে থাকে। আর খুব বড় batch size গড় throughput ভালো দেখালেও tail latency ও memory spike বাড়িয়ে দেয়; সবসময় একটা max batch size ও flush timeout রাখো।

বাস্তব উদাহরণ

Kafka-র পুরো consumer model pull-based — consumer নিজের গতিতে data টানে, তাই স্বাভাবিকভাবেই backpressure তৈরি হয়; কোনো slow consumer পুরো সিস্টেমকে ডোবায় না। আর producer-এ batching (linger.ms, batch.size) দিয়ে throughput নাটকীয়ভাবে বাড়ানো হয়।

TCP নিজেই backpressure-এর ক্লাসিক উদাহরণ — flow control (receive window) আর congestion control দিয়ে sender-কে receiver ও network-এর গতির সাথে মানিয়ে চলতে বাধ্য করে।

ডাটাবেস (Postgres COPY, MySQL bulk insert) ও logging system batching দিয়ে millions of rows/events দক্ষভাবে লেখে। বড় online সার্ভিসগুলো (যেমন Netflix-এর adaptive concurrency limits) overload-এ load shedding করে p99 ঠিক রাখে।

টিপস

ইন্টারভিউতে "producer fast, consumer slow হলে কী করবে?" — উত্তরে শুধু "queue বাড়াব" বোলো না, এটা ফাঁদ। বলো: bounded queue + backpressure, প্রয়োজনে load shedding। আর batching ব্যাখ্যার সময় সবসময় throughput-vs-latency trade-off উল্লেখ করো এবং "size + time dual flush trigger"-এর কথা বললে তুমি বাস্তব অভিজ্ঞতার ছাপ রাখবে।

মিনি কুইজ

1. Unbounded queue ব্যবহারের মূল বিপদ কী?

2. Batching কীভাবে per-item cost কমায়?

3. Load shedding কখন প্রয়োগ করা হয়?