শুরুর দিকে সব Application-ই অনেক শান্তশিষ্ট থাকে। একটা VM, একটা Database। সব Request ওই একটাই Server-এ যায়। সবাই সুখে শান্তিতে আছে। CPU ২০%। RAM অর্ধেক খালি। Production-এ Alert বলতে মাঝে মাঝে SSL Renew করার Reminder। জীবন সুন্দর!
তারপর...
একদিন Marketing Team কোনো Campaign চালাইলো। আর হঠাৎ করেই Traffic পাঁচগুণ; তারপর দশগুণ। CPU 95% Response Time বাড়তেছে। Users Complaint করা শুরু করছে। এইখানে সাধারণত প্রথম চিন্তা আসে - "আরেকটা Server দেই।"
খারাপ আইডিয়া না।
একটা Load Balancer বসানো হইলো সামনে। পিছনে দুইটা App Instance। তারপর চারটা। তারপর আটটা। Request যেই আসে, Load Balancer ঘুরাইয়া যে Server ফাঁকা পাইতেছে, ওইখানে পাঠায় দিতেছে।
একটা Request Server A-তে। পরেরটা Server C-তে। তারপর Server B।
চমৎকার!
কিন্তু একটা শর্ত আছে।
App-টা Stateless হইতে হবে। কারণ Load Balancer-এর কোনো গ্যারান্টি নাই যে পরের Request আবার একই Server-এ যাবে।
আজ Login Session যদি RAM-এ রাখে Server A...
পরের Request Server D-তে গেলে তো সে কিছুই জানে না।
এইখানেই Redis ঢুকে পড়ে। কিন্তু, Redis কেন ঢুকল?
কারণ State-টাকে একটা Common জায়গায় রাখা দরকার।
Session, OTP, Cache, Temporary Data যেটা সব App Instance-ই দেখতে পারবে।
এখন Server কয়টা হইলো, ওইটা আর খুব গুরুত্বপূর্ণ না। State একটাই জায়গায়। সবাই একই Data দেখতেছে।
সমস্যা শেষ।
নাকি?
কয়েকদিন সবকিছু ঠিকঠাকই চলল। তারপর Support Team একটা Ticket দিল, "মাঝেমধ্যে Account Balance ভুল দেখাচ্ছে।" মাঝেমধ্যে, সবসময় না।
এই "মাঝেমধ্যে" জিনিসটা খুব প্যারা দেয়। কারণ Log দেখলেও কিছু পাওয়া যায় না। Retry দিলে আবার ঠিক।
Monitor ঠিকঠাক, CPU, Memory, Redis, Database, সব ঠিকঠাক। কিন্তু User ঠিক নাই।
বুঝায় বলি।
একটা Account-এ Balance আছে ২০০ টাকা। একই সময়ে দুইটা Request আসলো। একজন Withdraw করবে ১০০। আরেকজন Withdraw করবে ১৮০। দুইজনই প্রথমে Database-এ গেল। দুইজনই দেখলো Balance = ২০০ দুইজনের কাছেই Information ঠিক। কেউ ভুল পড়ে নাই।
প্রথম Request হিসাব করলো ২০০ - ১০০ = ১০০
দ্বিতীয় Request হিসাব করলো ২০০ - ১৮০ = ২০
এখন ধরা যাক প্রথম Request আগে Write করল। Database-এ Balance হইলো ১০০
এক সেকেন্ডও যায় নাই। দ্বিতীয় Request তার পুরান হিসাব অনুযায়ী Write করলো ২০ অথবা উল্টাটাও হইতে পারে। শেষে Balance ১০০। কিংবা ২০।
দুইটার কোনোটাই আসলে সঠিক History না। কারণ দুইজনই Valid Operation করছিল। কেউ Corrupt Data লেখে নাই। তবুও একটা Update উধাও। মনে হয় যেন কেউ মাঝখান থেকে History ফেলে দিছে।
এইখানেই আসল ঝামেলা। প্রথমে মনে হইতে পারে Redis-এর সমস্যা। আসলে না। Redis ঠিকই আছে। Database-ও ঠিক। সমস্যা Data-তে না। সমস্যা Timing-এ। দুইটা Request একই Reality দেখছে। দুইজনই ভাবতেছে - "আমার কাছেই Latest Data আছে।" শেষে যে লিখতেছে...
সেই জিততেছে। আগের Update? গায়েব।
এই ধরনের সমস্যার একটা নাম আছে, Race Condition
যে আগে Finish Line পার হয়, সেই Winner। বাকিরা যতই সঠিক হোক, History বদলাইতে পারে না।
এই পর্যন্ত আসলে Redis কোনো Problem Solve করে নাই। Redis শুধু Shared State দিল। কিন্তু Shared State মানেই Shared Problem। আগে একটা Server ভুল করতে পারত। এখন আটটা Server একই সাথে ভুল করতে পারে। স্কেল বাড়ার সাথে সাথে Bug-ও Parallel হয়ে গেল। মারহাবা!
এইখানে সাধারণত একটা আইডিয়া আসে। "Lock দিলেই তো হয়।" খুবই যুক্তিযুক্ত। যে Account Update করবে, আগে একটা Lock নিবে। Lock থাকলে অন্য কেউ অপেক্ষা করবে। কাজ শেষ, Lock ছাড়ল। তারপর পরের Request, Serial Processing; একদম Queue-এর মতো Behaviour। Redis এই কাজটা অনেক বছর ধরেই করে আসতেছে। সবচেয়ে পরিচিত Pattern হইলো SETNX
মানে - যদি Key আগে না থাকে, তাহলেই Set করে।
ধরেন Account ID 123
Request এসে বলো, lock:account:123 যদি Lock না থাকে, Redis বলবে - ঠিক আছে Lock নাও।
আরেকটা Request একই সময়ে আসল, সে একই Lock চাইলো। Redis বললো, না ভাই আগেই কেউ নিছে। অপেক্ষা করেন।
সুন্দর না?
কিন্তু Production কখনো এত ভদ্র না। ধরেন, Process Crash করলো, Lock Release করার আগেই। এখন?
Lock তো পড়ে আছে। আর কেউ ঢুকতে পারতেছে না। এইজন্য Lock-এর সাথে Expiration দেওয়া হয়। TTL শেষ, Lock নিজে নিজে গায়েব।
আবার আরেকটা Problem, যদি Operation-টাই TTL-এর চেয়ে বেশি সময় নেয়? Lock শেষ, অন্য Process ঢুকে গেল।
দুইজন আবার একসাথে কাজ শুরু করলো। আবার সেই একই গল্প।
আরেকটু Production-Grade করতে গেলে Lua Script ব্যবহার করা হয়। কারণ Lock Release-ও Atomic হওয়া দরকার। যে Lock নিছে, সেই যেন Release করে। অন্য কেউ না।
Redis Lua Script এইখানে অনেক সুন্দর একটা সমাধান দেয়। সবকিছু এক Command-এর মতো Execute হয়। মাঝখানে কেউ ঢুকতে পারে না।
এরপর অনেকে বলে, "Redlock ব্যবহার করলেই তো সব শেষ।" Redlock আসলে Multiple Redis Node-এর উপর Lock Coordination করার একটা Algorithm। উদ্দেশ্য হইলো, একটা Redis মারা গেলেও Lock যেন একেবারে ভেঙে না পড়ে। ছোট থেকে মাঝারি অনেক System-এ এই Approach বেশ ভালোই কাজ করে, বিশেষ করে যখন Lock-এর Lifetime ছোট, Critical Section ছোট, আর Failure Model মোটামুটি Simple।
কিন্তু এক মিনিট। এতক্ষণ তো একটা Redis ধইরাই কথা হচ্ছিল। Redis নিজেই যদি Distributed হয়? একটা Node Down হইলে? Network ভাগ হইলে? কে ঠিক করবে কোন Lock আসল, কোনটা পুরান, কোনটা নতুন?
মজার ব্যাপার হইলো, এই প্রশ্নের উত্তর খুঁজতে খুঁজতেই Industry আরেকটা রাস্তা ধরছিল।
এই জায়গায় এসে সাধারণত টিমের মধ্যে একজন থাকেই, যে সব সমস্যার একটাই Solution দেখে। "Queue দেই।" মজার ব্যাপার হইলো, অনেক সময় এই কথাটাই ঠিক।
ধরেন Withdraw Request আর Database-এ সরাসরি যাচ্ছে না। আগে Kafka-তে ঢুকতেছে, তারপর Consumer একটা একটা করে Process করতেছে। আগের উদাহরণটাই ধরি। Balance ২০০। দুইটা Withdraw আসল, একটা ১০০ আরেকটা ১৮০। দুইটাই আগে Kafka-তে গেল। Queue-তে Order আছে, আগে যেটা ঢুকছে, আগে সেইটাই Process হবে।
Consumer প্রথম Request Process করল। Balance হইলো ১০০। তারপর দ্বিতীয়টা। এখন সে ২০০ দেখে হিসাব করবে না, সে দেখবে ১০০। তারপর Business Logic যা বলে সেটাই হবে - Insufficient Balance হলে Reject, Allow করলে সেভাবেই Update। WoW!
Race Condition-এর একটা বিশাল অংশই উধাও। কারণ একই Data একসাথে দুইজন Modify করার সুযোগই পাচ্ছে না।
এই পর্যন্ত শুনলে মনে হবে, ব্যাস, এবার তো সত্যিই শেষ। নাকি?
Wait! Kafka-ও তো একটা Server না। Production-এ একটা Kafka Broker দিয়া কেউ বসে থাকে না, Cluster হয় - Multiple Broker, Multiple Partition, Replication। মানে আবার Distributed System।
একটা Topic আছে, তার চারটা Partition। প্রতিটা Partition-এর একটা Leader আছে, আর কয়েকটা Replica। Producer Leader-এর কাছে লিখে, Follower-রা Copy করে। সব ঠিকঠাকই চলতেছে।
হঠাৎ Leader মারা গেল। এখন? নতুন Leader কে? যদি দুইজন Broker-ই ভাবে, "আমি Leader," তখন? দুই জায়গায় দুইরকম Message ঢুকতে শুরু করলে Ordering, History, Consistency - আবার সেই একই গল্প।
Kafka-র সবচেয়ে বড় শক্তিগুলোর একটা হইলো একটা Partition-এর ভিতরে Message Order ধরে রাখা। কিন্তু সেই Order ধরে রাখতেই তো আগে ঠিক করতে হবে, Leader কে।
অনেকদিন Kafka এই কাজের জন্য ZooKeeper ব্যবহার করত - মানে Kafka-র বাইরেই আরেকটা Distributed System, যে Leader Election করত, Metadata রাখত, Cluster Manage করত। পরে Kafka Team নিজেরাই ভাবল, এত Dependency নিয়ে লাভ কী? এখন Modern Kafka-তে আসে KRaft
ZooKeeper ছাড়াই Kafka নিজের Cluster Metadata Manage করতে পারে। আবার সেই Election, আবার Majority, আবার Log - শুধু নাম আলাদা।
খেয়াল করলে একটা Pattern চোখে পড়ে। Redis Lock-এর সময়ও একই প্রশ্ন ছিল, Kafka-তেও একই প্রশ্ন। কে আগে? কে পরে? কে ঠিক?
এই পর্যন্ত Redis-এ ছিলাম, Kafka-তেও ঘুরলাম। এবার Redis-ও বাদ, Kafka-ও বাদ। চলেন Database-এ যাই।
একটা PostgreSQL Server, সব Write ওইখানে যায়। আর কয়েকটা Replica, Read Traffic যায় Replica-তে। Classic Setup, Production-এ খুবই Common।
দিন ভালোই যাচ্ছিল। তারপর একদিন Primary Machine হঠাৎ বন্ধ। Power গেল, Disk গেল, নাকি Kernel Panic - যাই হোক, Primary নাই। এখন? System তো থেমে থাকতে পারে না। কাউকে না কাউকে Primary বানাইতে হবে।
শুনতে সহজ - যে Replica সবচেয়ে Updated, সেই Primary। ব্যাস। কিন্তু সব Replica কি একই জায়গা পর্যন্ত Sync ছিল? যদি না থাকে?
ধরেন তিনটা Replica - A, B আর C। Network-এ একটু সমস্যা হইল, A আর B একদিকে, C অন্যদিকে। Primary-এর সাথে কার Connection ছিল? কে শেষ Transaction পাইছে, কে পায় নাই? সবসময় এত পরিষ্কার না।
আরও মজা। যদি A ভাবে, "Primary নাই, আমি নতুন Primary," আর একই সময়ে B-ও ভাবে, "না, আমি" - তাহলে? দুইটা Primary, দুইজনই Write নিচ্ছে, দুইজনের History আলাদা। তারপর Network আবার ঠিক হয়ে গেল। এখন কোন History আসল? কোনটা ফেলা হবে?
এই অবস্থার একটা সুন্দর নাম আছে, Split Brain
নামটা শুনলেই একটু Horror Movie-এর মতো লাগে, আর Production-এ এই জিনিস আসলেই একদম Horror, কারণ দুই পাশই নিজেদের ঠিক মনে করে। কেউ ইচ্ছা করে ভুল করছে না, কেউ Corrupt না। তারপরও System দুইটা Reality বানিয়ে ফেলছে।
এইখানে এসে একটু থামা যায়। কারণ একটা অদ্ভুত জিনিস বারবার ঘটতেছে - Redis-এও, Kafka-তেও, PostgreSQL-তেও। সমস্যাগুলা আলাদা দেখায়, কিন্তু ভিতরে ভিতরে একইরকম। কেউ Lock নিতেছে, কেউ Leader বানাইতেছে, কেউ Primary ঠিক করতেছে, কেউ Queue Maintain করতেছে। নাম আলাদা, Technology আলাদা, কিন্তু ঝগড়া একই - History আগে কে লিখবে? আর সবাই কি সেই একই History মেনে চলবে?
এইখানেই মনে হয়, "আরে! এতক্ষণ ধরে আসলে একই সমস্যারই বিভিন্ন Version দেখা গেল!" আর ঠিক এই জায়গা থেকেই গল্পটা অন্যদিকে মোড় নেয়, কারণ এতক্ষণ একটা শব্দ ইচ্ছা করেই এড়িয়ে যাওয়া হচ্ছিল - Consensus
Redis, Kafka, PostgreSQL দেখতে তিনটা সম্পূর্ণ আলাদা Technology কিন্তু একটু দূর থেকে তাকাইলে একটা জিনিস চোখে পড়ে - কেউ Cache নিয়া ব্যস্ত, কেউ Message নিয়া, কেউ Database নিয়া। কিন্তু সবার ভেতরের প্রশ্ন একই - "আগে কোন ঘটনা ঘটল?"
এইটাই।
Storage না, Replication না, Database Engine না - Ordering; কারণ History যদি সবাই একইভাবে না দেখে, তাহলে Storage যতই Reliable হোক লাভ নাই, Replication যতই Fast হোক লাভ নাই। সবাই যদি আলাদা History বিশ্বাস করে, তাহলেই খেলা শেষ।
এইখানে "Consensus" শব্দটা ঢুকে পড়ে। অনেক সময় Consensus শুনলেই মাথায় Mathematics, Paxos, Paper, Formal Proof এসব ঘুরতে থাকে। আসলে দৈনন্দিন Production-এ ব্যাপারটা অনেক সহজ - একদল Machine-কে একটা একই History-তে রাজি করানো। কে আগে লিখল, কে পরে লিখল, কোন Event Valid, কোনটা না।
Industry-তে Consensus বলতে অনেকের প্রথমে Raft-ই মনে পড়ে। কারণ Paper-টা লেখা হয়েছিল একটা লক্ষ্য নিয়ে - Consensus-কে মানুষের বোঝার মতো করা, Paxos-এর তুলনায় অনেক সহজভাবে।
Raft-এর গল্পটাও খুব সোজা। একজন Leader, বাকিরা Follower। সব Write আগে Leader-এর কাছে যাবে, Leader নিজের Log-এ লিখবে, তারপর Followers-দের বলবে, "এইটা Copy করো।" Followers Copy করল। কিন্তু Copy করলেই শেষ না, Majority পর্যন্ত পৌঁছাইতে হবে।
ধরেন পাঁচটা Node, Leader সহ মোট পাঁচজন। কমপক্ষে তিনজন যদি Event-টা লিখে ফেলে, তখন Leader বলবে, "ঠিক আছে, এইটা Commit।" তারপর Client Success Response পাবে।
এইখানে Majority-র আইডিয়াটা দারুণ। কারণ দুইটা Machine মারা গেলেও তিনটা থাকলে Cluster এখনও সিদ্ধান্ত নিতে পারে। সবাইকে Alive লাগবে না, শুধু Majority।
আর Leader যদি মারা যায়? Follower-রা বসে থাকবে না, কিছুক্ষণ Wait করবে, আর Leader আর না ফিরলে Election শুরু হবে। যে Majority Vote পাবে, সে নতুন Leader। তারপর আবার Log চলতে থাকবে - History একই থাকবে, শুধু Leader বদলাবে।
এই কারণেই Raft এত জনপ্রিয় - etcd, Consul, Nomad, Kubernetes Control Plane। অনেক Distributed System-এর ভিতরে কোথাও না কোথাও Raft বসে আছে। কারণ Algorithm-টা Predictable, Debug করা তুলনামূলক সহজ, Implementation-ও Mature - বহু বছর ধরে Production Battle Tested।
কিন্তু কোনো কিছুই Free না, Raft-এরও দাম আছে। Leader আছে মানে সব Write আগে Leader-এর কাছে যেতেই হবে। Leader একটু দূরে হলে Latency বাড়বে, Leader Overloaded হলে সবাই অপেক্ষা করবে, আর Leader Crash করলে Election - আর Election মানেই কয়েক মুহূর্ত Pause।
একটা Datacenter-এর মধ্যে এই Delay খুব বড় কিছু না। কিন্তু পৃথিবীর বিভিন্ন Continent-এ Node ছড়িয়ে গেলে - লন্ডন, সিঙ্গাপুর, সিডনি, ঢাকা - তখন? Leader যদি ইউরোপে থাকে, অস্ট্রেলিয়ার Write-ও আগে ইউরোপ ঘুরে আসবে। WAN Latency তখন বাস্তব সমস্যা।
আর Network Partition হলে? কোন পাশ Majority, কে Leader থাকবে, কে Wait করবে - সবই Raft Handle করে। কিন্তু Handle করার একটা Cost আছে, কিছুক্ষণের জন্য Progress থেমেও যেতে পারে।
Cloudflare-এর মতো কোম্পানির জন্য এই Pause-ও গুরুত্বপূর্ণ। কারণ তাদের Edge Location দুই-চারটা না, শত শত - বিশ্বজুড়ে।
EPFL-এর Research Paper। নামটা একটু অদ্ভুত, কিন্তু আইডিয়াটা বেশ ইন্টারেস্টিং।
সবচেয়ে বড় চিন্তার জায়গা - Leader কি সত্যিই বাধ্যতামূলক? নাকি Leader থাকলে ভালো, কিন্তু না থাকলেও System চলতে পারে?
Raft-এ Leader হলো কেন্দ্র। QuePaxa-তে Leader অনেকটা Optimization, Requirement না।
Paper-এর ভিতরে অনেক Mathematics, Formal Proof, আর Algorithmic Detail আছে - সেইদিকে না যাই। মূল Philosophy-টাই বেশি মজার।
একাধিক Node একই সময়ে Progress করতে পারে, Conflict হলে সুন্দরভাবে Merge করা যায়, আর যেখানে সম্ভব Parallelism কাজে লাগানো হয়। অর্থাৎ Concurrency-কে শত্রু না ভেবে, Control করার চেষ্টা। Paper-এ এই ধারণাটাকে Constructive Concurrency হিসেবে উপস্থাপন করা হয়েছে - মানে সবকিছু জোর করে Serial না বানিয়ে, যতটুকু Parallel হওয়া নিরাপদ, ততটুকু Parallel-ই থাকুক। Leader থাকলে ভালো, না থাকলেও পৃথিবী থেমে যাবে না।
এটা Raft-এর Replacement, এমন কথা Paper-ও বলে না। বরং অন্য ধরনের Trade-off, বিশেষ করে Large Geo-Distributed Environment-এর জন্য।
Cloudflare তাদের Global Infrastructure-এর জন্য নতুন ধরনের Coordination Layer নিয়ে Experiment করতেছিল, যেখানে Traditional Leader-Centric Approach-এর সীমাবদ্ধতা একটু কমানো যায় - বিশেষ করে Edge Network-এ।
সেখান থেকেই Meerkat; QuePaxa-এর ধারণা থেকে অনুপ্রাণিত, কিন্তু একেবারে Paper Copy না - বরং বাস্তব Production Environment-এর জন্য Engineering করা একটা System।
Cloudflare Blog-এ যেটা সবচেয়ে ভালো লাগছে, তারা শুধু "নতুন Algorithm বানাইছি" এই ধরনের Marketing করে নাই - বরং Engineering Journey-টাই দেখাইছে। কোথায় Raft ভালো, কোথায় Limitation, কেন অন্য কিছু Try করা দরকার মনে হলো, আর কোথায় Meerkat Fit করতে পারে।
তবে একটা কথা মনে রাখা দরকার - Meerkat এখনও Experimental। কালকেই etcd Uninstall করার কোনো কারণ নাই। Raft হঠাৎ করে খারাপ হয়ে যায় নাই, বরং Industry-তে এখনও সবচেয়ে Proven Consensus Algorithm-গুলোর একটা। তারচে বড় কথা Meerkat এখনও পাবলিশ হয় নাই।
কিন্তু অনেকদিন পর Consensus নিয়ে সত্যিই নতুন কিছু দেখা গেল। সেটাই বেশি Interesting।
Meerkat Introductory Blog: https://blog.cloudflare.com/ meerkat-introduction/
Author: Rakib Yeasin