০০ যে প্রশ্নগুলোর উত্তর খুঁজবো
সেশন ০৮ এ আমরা queue শিখেছি। একটা job queue তে যায়, আর একজন worker সেটা তুলে নিয়ে কাজ করে। কিন্তু একটা ঘটনার খবর যদি একসাথে পাঁচ জায়গায় পৌঁছাতে হয়? আর ছয় মাস পরে আরো তিনটা নতুন জায়গা যোগ হয়?
এই সেশনে আমরা দেখবো, একটা service কীভাবে শুধু “এটা ঘটেছে” বলে চুপ করে যায়, আর যার দরকার সে নিজেই খবরটা নিয়ে নেয়। এই ধরনের design কে বলে event-driven architecture। শেষে আপনি বুঝবেন, Kafka কেন এত জনপ্রিয়, আর কোথায় এই পথে না যাওয়াই ভালো।
- ১ Order service নিজেই সবাইকে call করলে সমস্যা কোথায়? (tight coupling)
- ২ "এটা করুন" আর "এটা ঘটেছে" এর মধ্যে পার্থক্য কী? (command, event)
- ৩ একটা event অনেক service এর কাছে পৌঁছায় কীভাবে? (pub/sub, topic)
- ৪ Kafka কেন queue নয়, একটা log? (partition, offset, consumer group, retention, replay)
- ৫ একই order এর event গুলো ঠিক ক্রমে পৌঁছাবে তো? (ordering per key)
- ৬ কেউ নির্দেশ না দিলে পুরো delivery flow চলে কীভাবে? (choreography)
- ৭ Event এর format বদলালে কী হয়? (schema evolution, event-carried state)
- ৮ শুধু event জমিয়ে রাখলে কেমন হয়? (event sourcing)
- ৯ কখন event ব্যবহার করবেন না?
০১ Order service নিজেই সবাইকে call করলে সমস্যা কোথায়?
রমজানের এক বিকেল, ৫টা ৪০। ইফতারের আর বিশ মিনিট বাকি। “ঝটপট” নামের food delivery app এ প্রতি সেকেন্ডে প্রায় ৫০টা order ঢুকছে। রুবেল অফিসে বসে dashboard দেখছে, আর তার বুক ধুকপুক করছে। হঠাৎ checkout এর graph লাল হয়ে গেলো। SMS gateway ধীর হয়ে গেছে, আর সেই কারণে মানুষ order ই দিতে পারছেন না। ঠিক তখন Slack এ loyalty team এর মেসেজ এলো: “ভাই, order হলে আমাদের service কেও একটা call দিয়েন।”
রুবেল order service এর দায়িত্বে। দুই বছর আগে এই service এর কাজ ছিলো একটাই: order টা database এ লেখা। তারপর একে একে অনুরোধ এসেছে। Restaurant কে জানাতে হবে। Rider খুঁজতে হবে। Customer কে SMS দিতে হবে। Loyalty point দিতে হবে। Analytics এ হিসাব পাঠাতে হবে। প্রতিটা অনুরোধে রুবেল order service এ আরেকটা call যোগ করেছে:
এই design এ তিনটা সমস্যা লুকিয়ে আছে:
- সবচেয়ে ধীর service সবাইকে ধীর করে: Order service পাঁচটা call একটার পর একটা করে। সব মিলিয়ে প্রায় 990ms লাগে, আর এর বেশিরভাগটাই SMS gateway এর। খারাপ দিনে SMS gateway 5 সেকেন্ড পর timeout দেয়, তখন customer ছয় সেকেন্ড ধরে spinner দেখেন।
- একজন down হলে order ই হয় না: প্রতিটা service আলাদাভাবে 99.9% সময় ঠিক থাকে। কিন্তু order সফল হতে পাঁচটাকেই একসাথে ঠিক থাকতে হয়। ০.৯৯৯ কে পাঁচবার গুণ করলে আসে প্রায় ০.৯৯৫, মানে ৯৯.৫%। দিনে দেড় লাখ order হলে প্রায় ৭৫০টা order শুধু এই কারণে fail করে। অথচ SMS না গেলেও খাবারটা পৌঁছাতে কোনো বাধা ছিলো না।
- প্রতিটা নতুন team এর জন্য order service বদলাতে হয়: Loyalty team এর জন্য নতুন code, নতুন deploy, নতুন ঝুঁকি। পরের মাসে fraud team আসবে। Order service এর মূল কাজ ছোট, কিন্তু তাকে বাকি সবার খবর রাখতে হয়।
একটা service যখন অন্য অনেক service কে নাম ধরে চেনে আর তাদের উপর নির্ভর করে, তখন তাকে বলে tight coupling। একটার সমস্যা তখন সবার সমস্যা হয়ে যায়।
Order service এর আসল কাজ order নেওয়া। SMS গেলো কিনা, point যোগ হলো কিনা, সেগুলো তার মাথাব্যথা হওয়ার কথা না। কিন্তু এই design এ order service কে সবার কাজ শেষ হওয়া পর্যন্ত অপেক্ষা করতে হয়, আর নতুন কেউ এলে নিজের code বদলাতে হয়।
০২ “এটা করুন” আর “এটা ঘটেছে” এর মধ্যে পার্থক্য কী?
রুবেলের পুরোনো code এ order service প্রত্যেককে একটা করে নির্দেশ পাঠায়: “rider খুঁজুন”, “SMS পাঠান”, “point দিন”। এমন নির্দেশকে বলে command।
এবার একটু অন্যভাবে ভাবুন। Order service শুধু একটা ঘোষণা দিলো: “Order #5821 এইমাত্র হয়েছে।” কাকে কী করতে হবে, সে বললো না। যার দরকার, সে নিজেই শুনে নিজের কাজ করবে। এমন ঘোষণাকে বলে event।
| Command | Event | |
|---|---|---|
| নামের ধরন | ক্রিয়া, ভবিষ্যৎ: AssignRider | অতীত কাল: OrderPlaced |
| মানে | “এই কাজটা করুন” | “এই ঘটনাটা ঘটে গেছে” |
| কতজন পায় | ঠিক একজন, যাকে আগে থেকে ঠিক করা | যে খুশি, শূন্য জনও হতে পারে |
| পাঠানো service কি প্রাপককে চেনে? | হ্যাঁ, নাম ধরে চেনে | না, চেনার দরকার নেই |
| প্রাপক কি “না” বলতে পারে? | হ্যাঁ, কাজটা fail করতে পারে | না, ঘটনাটা ঘটেই গেছে |
পাড়ায় রিকশায় মাইক বেঁধে যে ঘোষণা হয়, সেটার কথা ভাবুন: “আগামীকাল সকাল দশটা থেকে পানি থাকবে না।” যিনি ঘোষণা দিচ্ছেন, তিনি আলাদা করে কোনো বাড়িতে ফোন করেন না। কে শুনলো, সেটাও তিনি জানেন না। যার দরকার, তিনি শুনে আগেভাগে পানি ধরে রাখেন। Event ঠিক এমনই একটা ঘোষণা।
Event এর নাম সবসময় অতীত কালে হয়: OrderPlaced, PaymentReceived, RiderAssigned। নামটা অতীত কালে লিখতে গিয়ে আটকে গেলে বুঝবেন, আপনি আসলে একটা command পাঠাতে চাইছেন।
০৩ একটা event অনেক service এর কাছে পৌঁছায় কীভাবে?
সেশন ০৮ এর queue এ একটা নিয়ম ছিলো: একটা job একজন worker ই পায়। Worker রা একই কাজের জন্য একে অপরের সাথে প্রতিযোগিতা করে। Video encode এর জন্য এটা ঠিক আছে, কারণ একটা video একবার encode করলেই যথেষ্ট।
কিন্তু রুবেলের OrderPlaced event টা restaurant, rider, SMS, loyalty আর analytics, সবারই দরকার। এখানে চাই উল্টো নিয়ম: প্রত্যেকে নিজের আলাদা কপি পাবে। এই নিয়মের নাম pub/sub (publish/subscribe)।
Pub/sub এ তিনটা শব্দ বারবার আসবে:
- Publisher: যে event পাঠায়। এখানে order service।
- Topic: একটা নাম দেওয়া চ্যানেল, যেখানে একই ধরনের event জমা হয়। যেমন, order সংক্রান্ত সব event যায়
orderstopic এ। Radio এর একটা চ্যানেলের মতো। - Subscriber: যে একটা topic এ নাম লেখায় (subscribe করে), আর সেই topic এর প্রতিটা event এর কপি পায়।
| Queue (সেশন ০৮) | Pub/sub | |
|---|---|---|
| একটা message কতজন পায় | একজন worker | প্রত্যেক subscriber একটা করে কপি |
| কী কাজে লাগে | ভারী কাজ ভাগ করে দেওয়া | একটা ঘটনার খবর অনেককে দেওয়া |
| নতুন consumer এলে | কাজ ভাগ হয়, প্রত্যেকে কম পায় | সে নিজের পুরো কপি পায় |
| উদাহরণ | video encode, PDF বানানো | OrderPlaced, PaymentReceived |
রুবেল এবার order service কে বদলে দিলো। Order service এখন order টা নিজের database এ লেখে, orders topic এ একটা OrderPlaced event publish করে, আর customer কে উত্তর দিয়ে দেয়। বাকি সবাই নিজেরাই topic থেকে event পড়ে নেয়:
ফলাফলটা দেখুন। Checkout এখন 990ms এর বদলে প্রায় 50ms এ শেষ হয়। SMS gateway আধা ঘণ্টা down থাকলেও order আসতে থাকে। SMS service ফিরে এসে জমে থাকা event গুলো পড়ে, আর দেরিতে হলেও SMS পাঠিয়ে দেয়। সবচেয়ে মজার ঘটনা ঘটলো পরের মাসে। Fraud team এসে শুধু orders topic এ subscribe করলো। রুবেলকে order service এর একটা লাইনও বদলাতে হলো না।
আগে order service জানতো কে কে তার খবর চায়। এখন সে জানে না, জানার দরকারও নেই। সে শুধু ঘটনাটা ঘোষণা করে। কে শুনবে, সেটা শ্রোতারা নিজেরা ঠিক করে। এতে নতুন subscriber যোগ করতে publisher কে বদলাতে হয় না।
০৪ Kafka কেন queue নয়, একটা log?
রুবেলের team topic চালানোর জন্য Kafka বেছে নিলো। সেশন ০৮ এ এক লাইনে বলা হয়েছিলো, Kafka ঠিক queue নয়, একটা log। এবার কথাটার মানে খুলে দেখি।
Log মানে একটা খাতা
এখানে log মানে error log না। Log মানে এমন একটা খাতা, যেখানে লেখা শুধু শেষে যোগ হয়। আগের কোনো লাইন মোছা বা বদলানো হয় না। দোকানের হিসাবের খাতার মতো: নতুন বিক্রি হলে নিচে নতুন লাইন লেখা হয়।
Queue আর log এর সবচেয়ে বড় পার্থক্য হলো, পড়লে কী হয়। Queue তে worker ACK পাঠালে message মুছে যায়। Kafka তে কেউ পড়লেও event মোছে না। খাতাটা যেমন ছিলো তেমনই থাকে। শুধু যে পড়ছে, সে মনে রাখে কোন লাইন পর্যন্ত পড়া হয়েছে।
এই ছবির চারটা শব্দ চিনে নিন:
- Partition: একটা topic এর ভেতরের এক একটা খাতা। একটা topic এ কয়েকটা partition থাকে, যাতে কাজ কয়েকটা machine এ ভাগ হয়ে যায়। প্রতিটা partition এর ভেতরে event গুলো লেখার ক্রমেই থাকে।
- Offset: Partition এর ভেতরে প্রতিটা event এর ক্রমিক নম্বর: ০, ১, ২, ৩… খাতার লাইন নম্বরের মতো। Consumer মনে রাখে, সে কোন offset পর্যন্ত পড়েছে।
- Retention: Kafka একটা event কতদিন রেখে দেবে, সেই সময়সীমা। Kafka এর default হলো ৭ দিন। সময় পার হলে পুরোনো event গুলো মুছে যায়, কেউ পড়ুক বা না পড়ুক।
- Replay: পুরোনো offset এ ফিরে গিয়ে event গুলো আবার শুরু থেকে পড়া। Event মোছে না বলেই এটা সম্ভব।
Replay কেন এত কাজের
ছবিতে নতুন Loyalty service টা দেখুন। সে আজই চালু হলো, কিন্তু সে চায় গত সাত দিনের order এর point ও দিতে। Queue হলে এটা অসম্ভব ছিলো, কারণ পুরোনো message গুলো অনেক আগেই মুছে গেছে। Kafka তে Loyalty শুধু বলে, “আমি সবচেয়ে পুরোনো offset থেকে পড়বো।” সাত দিনের সব order সে আবার পায়।
Replay আরেক জায়গাতেও খুব কাজে লাগে। ধরুন analytics service এর code এ একটা bug ছিলো, আর দুই দিন ধরে ভুল হিসাব হয়েছে। Bug ঠিক করে offset দুই দিন পিছিয়ে দিলেই সব হিসাব আবার ঠিকঠাক তৈরি হয়।
খরচ কেমন?
রুবেল একটা হিসাব করলো। দিনে দেড় লাখ order। প্রতি order এ গড়ে ৫টা event (placed, accepted, rider assigned, picked up, delivered)। প্রতিটা event প্রায় 1 KB।
- দিনে event: ১,৫০,০০০ × ৫, মানে সাড়ে সাত লাখ।
- দিনে জায়গা: সাড়ে সাত লাখ × 1 KB, মানে প্রায় 750 MB।
- ৭ দিনের retention: প্রায় 5.25 GB।
- নিরাপত্তার জন্য Kafka প্রতিটা partition এর ৩টা কপি রাখে: সব মিলিয়ে প্রায় 16 GB।
16 GB একটা সাধারণ laptop এর disk এও ধরে যায়। সাত দিনের পুরো ইতিহাস রাখার খরচ তাই খুবই কম।
Consumer group: কাজ ভাগ, আবার সবাই পুরোটা পায়
ইফতারের আগে প্রতি সেকেন্ডে ৫০টা order আসে। Rider service এর একটা machine এত event সামলাতে পারে না। তাই rider team তিনটা machine চালায়। কিন্তু একই event তিনটা machine পেলে একই order এর জন্য তিনজন rider পাঠানো হবে।
এর সমাধান consumer group। একই কাজের জন্য চলা কয়েকটা consumer একটা group এর নামে চলে। Kafka তখন partition গুলো group এর সদস্যদের মধ্যে ভাগ করে দেয়:
নিয়ম দুটো:
- একটা group এর ভেতরে প্রতিটা partition ঠিক একজন consumer পড়ে। তাই একটা event group এর মধ্যে একবারই process হয়। এটা queue এর মতো আচরণ।
- আলাদা group হলে প্রত্যেক group পুরো topic পায়। Rider group আর SMS group দুটোই সব event পায়। এটা pub/sub এর মতো আচরণ।
একটা মজার সীমাও আছে। Partition তিনটা হলে group এ চতুর্থ consumer যোগ করে লাভ নেই। সে কোনো partition পাবে না, অলস বসে থাকবে। তাই topic বানানোর সময়েই ভেবে নিতে হয়, ভবিষ্যতে সর্বোচ্চ কতজন consumer একসাথে লাগতে পারে।
Topic হলো একটা বই, আর partition গুলো বইয়ের অধ্যায়। প্রতিটা consumer group আলাদা একটা পাঠক দল। প্রত্যেক দল পুরো বইটা পড়ে, কিন্তু দলের ভেতরে অধ্যায়গুলো ভাগ করে নেয়। আর প্রত্যেক দল নিজের bookmark (offset) রাখে, তাই কেউ কারো গতির উপর নির্ভর করে না।
Kafka এর delivery নিয়েও সেশন ০৮ এর কথাটা খাটে। Consumer event process করে তারপর offset সেভ করে। মাঝখানে crash করলে একই event আবার আসে। তাই Kafka এর consumer কেও idempotent হতে হয়: একই OrderPlaced দুইবার এলে loyalty point যেন দুইবার যোগ না হয়।
০৫ একই order এর event গুলো ঠিক ক্রমে পৌঁছাবে তো?
এক সন্ধ্যায় একটা অদ্ভুত অভিযোগ এলো। একজন customer ৭:৪০:০১ এ order দিয়ে আট সেকেন্ড পরেই cancel করেছেন। তবু একজন rider রেস্টুরেন্টে গিয়ে হাজির। খাবার নেই, customer নেই, rider এর কুড়ি মিনিট নষ্ট।
রুবেল খুঁজে দেখলো, event দুটো দুই আলাদা partition এ গিয়েছিলো:
Kafka একটা কথাই নিশ্চিত করে: একটা partition এর ভেতরে event গুলো লেখার ক্রমেই পড়া হয়। কিন্তু দুই partition এর মধ্যে কোনো ক্রমের নিশ্চয়তা নেই। Partition 0 এ ৩০০টা event জমে থাকলে সেখানকার event দেরিতে পড়া হবে। Partition 1 খালি থাকলে সেখানকার event আগে পড়া হবে।
সমাধানটা সহজ। Event publish করার সময় একটা key দিতে হয়। Kafka key টাকে hash করে (একটা সংখ্যায় বদলে), partition এর সংখ্যা দিয়ে ভাগ করে, আর ভাগশেষ দেখে partition বেছে নেয়। সেশন ০৫ এ shard key দিয়ে ঠিক এভাবেই data ভাগ করেছিলেন। একই key সবসময় একই partition এ যায়। তাই order_id কে key বানালে order #5821 এর সব event একই partition এ, একই ক্রমে থাকে।
খেয়াল করুন, আমরা সব order এর মধ্যে ক্রম চাইনি। #5821 আর #5822 কোনটা আগে process হলো, তাতে কিছু যায় আসে না। আমাদের দরকার শুধু একই order এর ভেতরের ক্রম। এই ধারণার নাম ordering per key।
Topic এর partition সংখ্যা পরে বাড়ালে hash এর ভাগশেষ বদলে যায়। তখন একই order এর পুরোনো event এক partition এ, আর নতুন event অন্য partition এ চলে যেতে পারে। তাই partition সংখ্যা শুরুতেই একটু বেশি ধরে রাখুন। আর key এমন বাছুন, যাতে একটা key তে অস্বাভাবিক বেশি event না আসে (সেশন ০৫ এর hot key সমস্যা)।
০৬ কেউ নির্দেশ না দিলে পুরো delivery flow চলে কীভাবে?
একটা order এ শুধু OrderPlaced হয় না। Restaurant order টা accept করে। তারপর rider খোঁজা হয়। Rider পেলে customer কে জানানো হয়। এই ধাপগুলো একটার পর একটা হতে হবে।
পুরোনো design এ order service সবাইকে নির্দেশ দিতো, একজন conductor এর মতো। নতুন design এ কেউ কাউকে নির্দেশ দেয় না। প্রতিটা service শুধু নিজের পছন্দের event শোনে, নিজের কাজ করে, আর কাজ শেষে নিজের একটা নতুন event ছাড়ে:
এই পদ্ধতির নাম choreography। বিয়েবাড়ির নাচের কথা ভাবুন। গান বাজলে প্রত্যেকে জানে কোন তালে তার পা ফেলতে হবে। মঞ্চে দাঁড়িয়ে কেউ একে একে সবাইকে ডেকে বলে দিচ্ছে না, কখন কার পালা। প্রত্যেকে গানের সংকেত শুনে নিজের অংশটা করে।
এর উল্টো পদ্ধতি হলো orchestration, যেখানে একটা কেন্দ্রীয় service প্রত্যেককে ধাপে ধাপে command পাঠায়। দুটোর তুলনা:
| Choreography | Orchestration | |
|---|---|---|
| কে চালায় | কেউ না, প্রত্যেকে event শুনে নিজে চলে | একটা কেন্দ্রীয় service |
| নতুন ধাপ যোগ করা | নতুন service শুধু event শোনে | কেন্দ্রীয় service এর code বদলাতে হয় |
| পুরো flow দেখা | কঠিন, flow টা অনেক service এ ছড়ানো | সহজ, একটা জায়গাতেই লেখা |
| কোথায় মানায় | ছোট, সরল flow (৩-৪ ধাপ) | লম্বা, অনেক শর্তওয়ালা flow |
Choreography এর সবচেয়ে বড় downside হলো, পুরো ছবিটা কারো হাতে থাকে না। রুবেলের team এ একবার এমন হয়েছিলো: rider assign হচ্ছে না, কিন্তু কোন service এর কোন event আটকে আছে, তা খুঁজতে দুই ঘণ্টা লেগেছে। তাই প্রতিটা event এ order_id রাখুন, যাতে একটা order এর পুরো যাত্রা পরে খুঁজে বের করা যায়।
আরেকটা প্রশ্ন থেকে যায়: মাঝপথে কোনো ধাপ fail করলে কী হবে? যেমন, restaurant accept করলো, কিন্তু কোনো rider পাওয়া গেলো না। তখন আগের ধাপগুলো উল্টাতে হয়। এর জন্য saga নামের একটা আলাদা কৌশল আছে, যেটা আমরা সেশন ১৭ তে বিস্তারিত দেখবো।
০৭ Event এর format বদলালে কী হয়?
Schema evolution: পুরোনো পাঠককে না ভাঙিয়ে বদলানো
ছয় মাস পরে finance team চাইলো, OrderPlaced event এ payment method ও থাকুক (bKash, card, নাকি cash)। Event এর ভেতরে কোন কোন field থাকবে আর সেগুলো কোন ধরনের, এই কাঠামোকে বলে schema। আর সময়ের সাথে schema নিরাপদে বদলানোর কাজকে বলে schema evolution।
সমস্যাটা এখানে। Event এর publisher একজন, কিন্তু পাঠক ছয়জন। ছয়টা team একই দিনে deploy করবে, এটা আশা করা যায় না। তাই নতুন event পুরোনো পাঠকের কাছেও পড়ার মতো থাকতে হয়, আর পুরোনো event নতুন পাঠকের কাছেও। মনে রাখবেন, Kafka তে সাত দিনের পুরোনো event ও replay হতে পারে।
// v1: শুরুর event
{ "type": "OrderPlaced", "version": 1,
"order_id": 5821, "total": 450 }
// v2: নতুন field যোগ করা (নিরাপদ)
// পুরোনো consumer অচেনা field টা এড়িয়ে যায়।
// নতুন consumer field না পেলে default ধরে: "cash"
{ "type": "OrderPlaced", "version": 2,
"order_id": 5821, "total": 450,
"payment_method": "bkash" }
// ভুল পথ: field এর নাম বদলানো
// পুরোনো consumer "order_id" খোঁজে, পায় না, crash করে।
{ "type": "OrderPlaced", "version": 3,
"orderId": 5821, "amount": 450 }
নিরাপদ আর বিপজ্জনক বদলের তালিকা ছোট:
- নিরাপদ: নতুন field যোগ করা, যদি তার একটা default মান থাকে। পুরোনো consumer অচেনা field এড়িয়ে যায়।
- বিপজ্জনক: কোনো field মুছে ফেলা, নাম বদলানো, বা ধরন বদলানো (সংখ্যা থেকে text)। পুরোনো consumer যে field খোঁজে, সেটা আর পায় না।
বড় team গুলো এজন্য schema registry ব্যবহার করে। এটা একটা আলাদা service, যেখানে প্রতিটা event এর schema জমা থাকে। কেউ বিপজ্জনক বদল publish করতে চাইলে registry সেটা আটকে দেয়।
Event-carried state: event এ কতটুকু তথ্য রাখবেন
প্রথম দিকে রুবেলের event এ শুধু order_id থাকতো। SMS service কে customer এর phone number জানতে order service কে আবার call করতে হতো। Restaurant service কে খাবারের তালিকা জানতে আবার call। এই ছোট event কে বলে thin event।
হিসাবটা দেখুন। প্রতি সেকেন্ডে ৫০টা event, আর পাঁচটা consumer প্রত্যেকে একবার করে ফিরতি call করে। ফলে order service এর উপর প্রতি সেকেন্ডে ২৫০টা বাড়তি request আসে। তার চেয়েও খারাপ কথা, order service down হলে সবাই আবার আটকে যায়। যে coupling থেকে মুক্তি পেতে event এনেছিলাম, সেটা পেছনের দরজা দিয়ে ফিরে এসেছে।
সমাধান হলো দরকারি তথ্যটা event এর ভেতরেই পাঠানো: phone, address, খাবারের তালিকা, মোট দাম। একে বলে event-carried state transfer। Consumer কে আর কাউকে জিজ্ঞেস করতে হয় না। দরকারি data সে নিজের database এ রেখে দেয়।
এরও দাম আছে। Event বড় হয় (1 KB থেকে 2 KB)। একই data অনেক জায়গায় কপি থাকে। আর customer এর phone number এর মতো ব্যক্তিগত তথ্য অনেক service এ ছড়িয়ে পড়ে, যেটা নিরাপত্তার দিক থেকে ঝুঁকি। তাই প্রতিটা field এর জন্য প্রশ্ন করুন: কোনো consumer কি সত্যিই এটা চায়?
সব data event এ ঢুকিয়ে দেবেন না, আবার শুধু ID ও পাঠাবেন না। যে তথ্য বেশিরভাগ consumer এর লাগে, শুধু সেটুকু event এ রাখুন। বাকিটা যার দরকার, সে আলাদা করে চেয়ে নেবে।
০৮ শুধু event জমিয়ে রাখলে কেমন হয়?
এখন পর্যন্ত রুবেলের database এ প্রতিটা order এর বর্তমান অবস্থাটা লেখা থাকে: “order #5821, মোট 460 টাকা”। Event গুলো শুধু খবর পৌঁছানোর কাজ করে।
একটা ভিন্ন পথ আছে। বর্তমান অবস্থা না লিখে শুধু ঘটনাগুলো লিখে রাখা। আপনার পাড়ার মুদি দোকানের বাকির খাতার কথা ভাবুন। দোকানদার খাতায় “রহিম সাহেবের মোট বাকি ৮৫০ টাকা” লিখে রাখেন না। লেখেন: “৩ তারিখ চাল ৫০০”, “৭ তারিখ তেল ৪৫০”, “১০ তারিখ জমা ১০০”। মোট কত, সেটা লাইনগুলো যোগ করে বের করেন।
এই পদ্ধতির নাম event sourcing। এখানে event গুলোই আসল data। বর্তমান অবস্থা আলাদা করে জমা থাকে না, প্রয়োজনে event গুলো replay করে বের করা হয়।
এর লাভ হলো, কখনো কোনো তথ্য হারায় না। Customer যদি বলেন “আমার কাছ থেকে বেশি টাকা কেটেছে”, খাতা খুলে দেখা যায় কোন মুহূর্তে কী হয়েছিলো। ব্যাংকের লেনদেনের হিসাব এভাবেই রাখা হয়।
কিন্তু খরচও কম না। একটা পুরোনো account এ দশ হাজার event থাকলে প্রতিবার সব যোগ করা ধীর। তাই মাঝে মাঝে একটা snapshot (ঐ মুহূর্তের মোট হিসাব) রেখে দিতে হয়। পুরোনো event কখনো মোছা যায় না, তাই তাদের schema ও চিরকাল সামলাতে হয়। আর “আজকের সব বাতিল order দেখাও” এর মতো সাধারণ query ও সরাসরি চালানো যায় না।
Event-driven architecture আর event sourcing এক জিনিস নয়। প্রথমটা হলো service গুলোর মধ্যে খবর পাঠানোর পদ্ধতি। দ্বিতীয়টা হলো একটা service এর ভেতরে data জমা রাখার পদ্ধতি। রুবেলের পুরো app event-driven, কিন্তু শুধু payment এর হিসাবটা event sourcing এ রাখা যুক্তিসঙ্গত। বাকি সব জায়গায় সাধারণ table ই যথেষ্ট।
০৯ কখন event ব্যবহার করবেন না?
Event এর সুবিধা দেখে রুবেলের team এর এক junior সব কিছুতে event বসাতে চাইলো, এমনকি restaurant খোলা আছে কিনা সেই প্রশ্নেও। রুবেল তাকে থামালো। Event এর সবচেয়ে বড় বৈশিষ্ট্য হলো, publisher উত্তরের জন্য অপেক্ষা করে না। যেখানে উত্তর ছাড়া এগোনো যায় না, সেখানে event ভুল জিনিস।
Event ব্যবহার করবেন না, যখন...
- Caller এর এখনই উত্তর দরকার: Payment approve হলো কিনা, বা restaurant খোলা কিনা, সেটা না জেনে order confirm করা যায় না। এখানে সরাসরি call করুন।
- শোনার মতো মাত্র একজন আছে: একজন consumer এর জন্য topic বানানো মানে বাড়তি ঝামেলা। সরাসরি call বা সেশন ০৮ এর queue ই যথেষ্ট।
- User নিজের লেখা সাথে সাথে দেখতে চায়: Profile এর নাম বদলে page refresh করলে পুরোনো নাম দেখা গেলে user ভয় পায়। Event এর পথে এই দেরিটা থাকেই।
- Team ছোট, system ছোট: তিনজনের team আর দিনে ৫০০ order হলে Kafka চালানোর খরচ লাভের চেয়ে বেশি। একটা সাধারণ function call ই সবচেয়ে সহজ।
- কয়েকটা কাজ একসাথে সফল বা একসাথে বাতিল হতে হবে: Event এর পথে এটা অনেক কঠিন। এর জন্য saga নামের আলাদা কৌশল লাগে (সেশন ১৭)।
আরেকটা কথা মনে রাখুন। Event এর পথে সব কাজ একটু দেরিতে হয়। Order হওয়ার পর loyalty point যোগ হতে হয়তো দুই সেকেন্ড লাগে। এই সময়ে customer app খুললে পুরোনো point দেখবেন। একে বলে eventual consistency: সব জায়গা শেষমেশ একই অবস্থায় পৌঁছায়, তবে সাথে সাথে নয়। সেশন ০৩ এ replication lag এর সময় এটা প্রথম দেখেছিলেন। Event ব্যবহার করলে এই দেরিটা মেনে নিতে হয়, আর UI তে “point যোগ হচ্ছে” এর মতো বার্তা দেখাতে হয়।
Event-driven architecture একটা ঘটনার খবর অনেক service এর কাছে পৌঁছে দেয়, আর publisher কে সেই service গুলো চিনতে হয় না। এতে checkout দ্রুত হয়, একজনের সমস্যা সবার সমস্যা হয় না, আর নতুন team কাউকে না জিজ্ঞেস করেই যোগ দিতে পারে।
বিনিময়ে আপনি পান eventual consistency, duplicate event, ক্রমের মাথাব্যথা, schema সামলানোর দায়িত্ব, আর এমন একটা flow যেটা এক জায়গায় বসে দেখা যায় না। Caller এর এখনই উত্তর দরকার হলে event নয়, সরাসরি call করুন।
১০ মগজে প্রেশার দিন
আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“Kafka অনেক দ্রুত, প্রতি সেকেন্ডে লাখ লাখ message সামলাতে পারে। Queue ধীর, ছোট কাজের জন্য।”
গতির কথাটা সত্যি, কিন্তু আসল পার্থক্য গতিতে নয়।
যে জায়গাটা যোগ করা দরকার
আসল পার্থক্য হলো, পড়ার পর message এর কী হয়। Queue তে ACK এলে message মুছে যায়, আর একটা message একজন worker ই পায়। Kafka তে পড়ার পরও event retention এর সময় পর্যন্ত থেকে যায়। প্রতিটা consumer group নিজের offset রাখে, তাই একই event অনেক group পড়তে পারে। পুরোনো offset এ ফিরে গিয়ে replay ও করা যায়।
তাই “একটা কাজ একজন করবে” হলে queue মানানসই। “একটা ঘটনা অনেকে জানবে, আর দরকার হলে পরে আবার পড়বে” হলে Kafka এর মতো log মানানসই।
Queue হলো কাজের লাইন: কাজ শেষ, message শেষ। Log হলো ঘটনার খাতা: পড়া হলেও লাইন থেকে যায়, আর প্রত্যেক পাঠক নিজের bookmark রাখে।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“হ্যাঁ। একটা partition মানে একটাই খাতা, তাই সব event লেখার ক্রমেই পড়া হবে। সমস্যা মিটে গেলো।”
ক্রম ঠিক থাকবে, কথাটা সত্যি। কিন্তু এতে একটা বড় দাম দিতে হয়।
যে জায়গাটা যোগ করা দরকার
একটা partition একটা group এর ভেতরে মাত্র একজন consumer পড়তে পারে। তাই পুরো system এর গতি আটকে যায় একটা machine এর গতিতে। ইফতারের আগে প্রতি সেকেন্ডে ৫০টা order এলে, আর একটা machine সেকেন্ডে ২০টা সামলাতে পারলে, প্রতি সেকেন্ডে ৩০টা করে জমতে থাকবে।
আমাদের আসলে সব event এর মধ্যে ক্রম দরকার নেই। দরকার একই customer এর event গুলোর মধ্যে ক্রম। তাই customer_id কে key বানিয়ে অনেক partition রাখুন। একই customer এর event একই partition এ যাবে, আর ভিন্ন customer এর event অনেক machine এ সমান্তরালে process হবে।
পুরো system এর ক্রম খুব কম ক্ষেত্রেই দরকার হয়। কোন জিনিসের ভেতরে ক্রম দরকার, সেটা খুঁজে বের করুন। সেটাকেই key বানান।
উত্তর দেখুন আগে নিজে ভাবুন
সবাই যেভাবে ভাবে
“ঠিকই তো। Publisher subscriber কে চেনে না, subscriber publisher কে call করে না। দুজনের মধ্যে কোনো সম্পর্কই নেই।”
সরাসরি call এর বাঁধনটা সত্যিই কেটে যায়। কিন্তু আরেকটা বাঁধন থেকে যায়।
যে জায়গাটা যোগ করা দরকার
Service গুলো এখনো event এর format এর উপর নির্ভর করে। Order service OrderPlaced থেকে order_id field এর নাম বদলে দিলে পাঁচটা consumer একসাথে ভেঙে পড়বে, অথচ order service এর team হয়তো জানেই না কে কে এই field পড়ে। সরাসরি call এ অন্তত error সাথে সাথে চোখে পড়তো। এখানে ভুলটা চুপচাপ ঘটে।
তাই event এর schema কে একটা চুক্তি হিসেবে দেখতে হয়। শুধু নতুন field যোগ করুন, পুরোনো field মুছবেন না। Schema registry দিয়ে বিপজ্জনক বদল আটকান।
Event coupling মুছে দেয় না, শুধু জায়গা বদলায়। Service এর নাম থেকে coupling সরে যায় event এর format এ। সেই format কে public API এর মতো যত্ন করে রাখুন।
১১ যে টার্মগুলো শিখলাম
| টার্ম | মানে |
|---|---|
| Tight coupling | একটা service অন্য অনেক service কে নাম ধরে চেনে আর তাদের উপর নির্ভর করে, তাই একজনের সমস্যা সবার সমস্যা হয়। |
| Command | একজন নির্দিষ্ট প্রাপকের কাছে পাঠানো নির্দেশ: “এই কাজটা করুন”। প্রাপক কাজটা করতে ব্যর্থ হতে পারে। |
| Event | অতীতের একটা ঘটনার ঘোষণা: “এটা ঘটে গেছে”। যে খুশি শুনতে পারে, publisher কাউকে চেনে না। |
| Event-driven architecture | এমন design, যেখানে service গুলো একে অপরকে call না করে event এর মাধ্যমে খবর আদান-প্রদান করে। |
| Pub/sub | Publish/subscribe: publisher একটা topic এ event পাঠায়, আর প্রত্যেক subscriber নিজের আলাদা কপি পায়। |
| Topic | একটা নাম দেওয়া চ্যানেল, যেখানে একই ধরনের event জমা হয়, যেমন orders। |
| Partition | Topic এর ভেতরের এক একটা খাতা। এর ভেতরে event গুলো লেখার ক্রমেই থাকে। |
| Offset | Partition এর ভেতরে প্রতিটা event এর ক্রমিক নম্বর। Consumer মনে রাখে সে কোন offset পর্যন্ত পড়েছে। |
| Consumer group | একই কাজের কয়েকটা consumer এর দল। দলের ভেতরে partition ভাগ হয়, আর প্রতিটা দল পুরো topic পায়। |
| Retention | Kafka একটা event কতদিন রেখে দেবে (default ৭ দিন), কেউ পড়ুক বা না পড়ুক। |
| Replay | পুরোনো offset এ ফিরে গিয়ে event গুলো আবার পড়া। |
| Ordering per key | একই key এর সব event একই partition এ রেখে শুধু সেই key এর ভেতরে ক্রম ঠিক রাখা। |
| Choreography | কোনো কেন্দ্রীয় নির্দেশদাতা ছাড়া, প্রতিটা service event শুনে নিজের কাজ করে আর নতুন event ছাড়ে। |
| Orchestration | একটা কেন্দ্রীয় service প্রত্যেককে ধাপে ধাপে command পাঠিয়ে পুরো flow চালায়। |
| Schema / Schema evolution | Event এর ভেতরের field গুলোর কাঠামো, আর পুরোনো পাঠককে না ভাঙিয়ে সেই কাঠামো বদলানোর নিয়ম। |
| Schema registry | Event এর schema জমা রাখার service, যেটা বিপজ্জনক বদল আটকে দেয়। |
| Event-carried state transfer | দরকারি data event এর ভেতরেই পাঠানো, যাতে consumer কে ফিরতি call করতে না হয়। |
| Event sourcing | বর্তমান অবস্থা না লিখে শুধু ঘটনাগুলো জমা রাখা, আর replay করে বর্তমান অবস্থা বের করা। |
| Snapshot | Event sourcing এ কোনো এক মুহূর্তের মোট হিসাব, যাতে প্রতিবার শুরু থেকে replay করতে না হয়। |
১২ শর্ট সামারি
- Command একজন নির্দিষ্ট প্রাপককে কাজ করতে বলে, আর event শুধু জানায় যে কিছু একটা ঘটে গেছে। Pub/sub এ publisher একটা topic এ event পাঠায়, আর প্রত্যেক subscriber নিজের কপি পায়, তাই নতুন subscriber যোগ করতে publisher কে বদলাতে হয় না।
- Kafka একটা log: event শুধু partition এর শেষে যোগ হয়, পড়লেও মোছে না, আর retention শেষ হলে মোছে। প্রতিটা consumer group নিজের offset রাখে, group এর ভেতরে partition ভাগ হয়, আর পুরোনো offset এ ফিরে replay করা যায়।
- Kafka শুধু একটা partition এর ভেতরে ক্রম নিশ্চিত করে, তাই order_id এর মতো key দিয়ে একই জিনিসের সব event একই partition এ পাঠাতে হয়। Choreography তে কোনো কেন্দ্রীয় নির্দেশদাতা থাকে না, আর schema বদলাতে হয় শুধু default সহ নতুন field যোগ করে।
- Event এর পথে সব কিছু eventual consistency তে চলে, আর flow টা এক জায়গায় দেখা যায় না। Caller এর এখনই উত্তর দরকার হলে, বা শ্রোতা মাত্র একজন হলে, event ব্যবহার করবেন না।
১৩ মনে রাখুন
- Event অতীত কালে কথা বলে, command ভবিষ্যৎ কালে। নাম অতীত কালে লিখতে না পারলে সেটা event নয়।
- একজন কাজ করবে হলে queue, অনেকে জানবে হলে pub/sub। একই message এ দুই রকম চাহিদা থাকলে Kafka এর consumer group দুটোই দেয়।
- Kafka পড়ার পরও event রাখে। তাই নতুন service পুরোনো ইতিহাস replay করে শুরু করতে পারে, আর bug ঠিক করার পর আবার হিসাব করা যায়।
- ক্রম শুধু একটা partition এর ভেতরে। যে জিনিসের ভেতরে ক্রম দরকার, সেটাকেই key বানান।
- Choreography তে প্রতিটা event এ একটা ID রাখুন। নাহলে একটা আটকে যাওয়া order এর যাত্রা খুঁজে পাবেন না।
- Schema হলো চুক্তি। Default সহ নতুন field যোগ করুন, কিন্তু পুরোনো field মুছবেন না বা নাম বদলাবেন না।
- এখনই উত্তর দরকার হলে event নয়। Payment approve বা stock check এর মতো প্রশ্নে সরাসরি call করুন।