একটি VPS-এ NATS বনাম RabbitMQ বনাম Kafka
একটি VPS-এ কোন message queue নেবেন? delivery guarantee, memory ও disk খরচ, restart-এর পর আচরণ, backlog যাচাই এবং কখন Postgres-ই ভালো, তা জানুন।
একটি সার্ভারের জন্য সংক্ষিপ্ত উত্তর
একটি VPS-এ message queue বেছে নেওয়া speed-এর বিষয় নয়; এটি delivery guarantee-এর বিষয়। একটি server-এ broker সাধারণত bottleneck হয় না, কারণ আপনার application code, database এবং একমাত্র disk-ই সাধারণত আগে সীমায় পৌঁছে যায়। আপনি যে failure behaviour মেনে নিতে প্রস্তুত, সেই অনুযায়ী tool বেছে নিন। এরপর আপনার কাছে থাকা server-টির বাস্তব মাপ নিন।
অধিকাংশ reader-এর বিবেচনা করা উচিত এমন ক্রমে চারটি option:
- আপনি ইতিমধ্যে যে database চালাচ্ছেন, সেটিই ব্যবহার করুন।
SELECT ... FOR UPDATE SKIP LOCKED-সহ Postgres একটি কার্যকর job queue, এবং monitor করার জন্য এতে নতুন কোনো process যোগ হয় না। - RabbitMQ ব্যবহার করুন, যখন প্রতিটি message এমন একটি কাজের একক যা acknowledge করতে হবে, নির্দিষ্ট সীমার মধ্যে কয়েকবার retry করতে হবে, এবং শেষে এমন জায়গায় রাখতে হবে যেখানে কোনো মানুষ সেটি পরীক্ষা করতে পারেন।
- NATS ব্যবহার করুন, যখন message হলো event এবং আপনার system-এর একাধিক অংশ তাতে প্রতিক্রিয়া জানায়। যেসব event restart-এর পরেও টিকে থাকা আবশ্যক, সেগুলোর জন্য JetStream চালু করুন।
- কোনো downstream tool যদি শুধু Kafka protocol-এ কথা বলে, তখন Kafka ব্যবহার করুন। একটি server-এ এটিই প্রায় একমাত্র অবশিষ্ট কারণ।
এই guide-এর বাকি অংশে যুক্তিগুলো ব্যাখ্যা করা হয়েছে: ছোট VPS-এ প্রতিটি option memory ও disk-এর কত খরচ করে, server reboot হলে এটি কী করে, এবং users টের পাওয়ার আগেই backlog দেখানোর জন্য কোন exact command ব্যবহার করতে হবে।
ডেলিভারি গ্যারান্টি বলতে আসলে কী বোঝায়
At most once মানে হলো ব্রোকার মেসেজটি পাঠিয়ে দেয় এবং তা ভুলে যায়। যদি কোনো কনজিউমার সংযুক্ত না থাকে, অথবা কাজ শেষ হওয়ার আগেই কোনো কনজিউমার বন্ধ হয়ে যায়, তবে মেসেজটি হারিয়ে যায় এবং এর কোনো রিপোর্ট পাওয়া যায় না।
At least once মানে হলো কাজ সফলভাবে সম্পন্ন হওয়ার পর কনজিউমার একটি একনলেজমেন্ট (ack) পাঠায়। যতক্ষণ না সেই ack পৌঁছায়, ব্রোকার মেসেজটি নিজের কাছে রাখে এবং পুনরায় ডেলিভারি করার চেষ্টা করে। এই পুনরায় ডেলিভারির কারণেই আপনার হ্যান্ডলারগুলোকে অবশ্যই idempotent হতে হবে: একই মেসেজ দুইবার প্রসেস করার ফলে যেন কার্ড থেকে দুইবার টাকা না কাটে। এন্ড-টু-এন্ড Exactly once কোনো ব্রোকার আপনাকে সরাসরি দেয় না। এটি আপনার নিজস্ব ডাটাবেসে থাকা একটি ইউনিক কি (unique key) থেকে নিশ্চিত করতে হয়।
Replay একটি আলাদা বৈশিষ্ট্য। একটি কিউ (queue) থেকে মেসেজ একনলেজ হওয়ার সাথে সাথে তা মুছে ফেলা হয়। অন্যদিকে একটি লগ (log) মেসেজটিকে একটি নির্দিষ্ট রিটেনশন উইন্ডো পর্যন্ত সংরক্ষণ করে, যাতে নতুন কোনো কনজিউমার শুরু থেকে পুরো হিস্ট্রি পড়তে পারে। Kafka এবং NATS JetStream হলো লগ। RabbitMQ হলো একটি কিউ। থ্রুপুটের চেয়ে এই পার্থক্যটিই আর্কিটেকচারের ওপর বেশি প্রভাব ফেলে।
Dead lettering হলো সেই প্রক্রিয়া যা বারবার ব্যর্থ হওয়া মেসেজগুলোর ক্ষেত্রে ঘটে। এটি না থাকলে, একটি পয়জন মেসেজ (poison message) লুপে আটকে থাকে এবং এই লুপটিকে দেখে মনে হয় ওয়ার্কারটি ব্যস্ত, অথচ আসলে সেটি অকেজো হয়ে আছে।
Postgres দিয়ে শুরু করুন এবং ব্রোকারকে তার সক্ষমতা প্রমাণ করতে দিন
অধিকাংশ একক-অ্যাপ্লিকেশন ওয়ার্কলোড দিনে কয়েক হাজার ব্যাকগ্রাউন্ড জব নিয়ে গঠিত। এটি একটি টেবিলের মধ্যেই অনায়াসে রাখা সম্ভব।
CREATE TABLE job (
id bigserial PRIMARY KEY,
payload jsonb NOT NULL,
run_after timestamptz NOT NULL DEFAULT now(),
attempts int NOT NULL DEFAULT 0
);
CREATE INDEX job_ready_idx ON job (run_after, id);একজন ওয়ার্কার একটি ট্রানজ্যাকশনের ভেতরে একটি জব দাবি করে।
BEGIN;
SELECT id, payload
FROM job
WHERE run_after <= now()
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 1;
-- run the work, then remove the row
DELETE FROM job WHERE id = $1;
COMMIT;FOR UPDATE SKIP LOCKED হলো পুরো কৌশলের মূল ভিত্তি। এটি যে সারিটি রিটার্ন করে সেটিকে লক করে ফেলে এবং অন্য কোনো ট্রানজ্যাকশন ইতিমধ্যে লক করে রেখেছে এমন সারিগুলোকে এড়িয়ে যায়, ফলে দুইজন ওয়ার্কার কখনোই একই জব দাবি করতে পারে না। যদি কোনো ওয়ার্কার ক্র্যাশ করে, তবে Postgres তার ট্রানজ্যাকশন বাতিল (abort) করে দেয়, লকটি মুক্ত হয়ে যায় এবং সারিটি পরবর্তী ওয়ার্কারের জন্য দৃশ্যমান হয়। আপনি পাচ্ছেন at-least-once ডেলিভারি, attempts বাড়িয়ে রিট্রাই করার সুবিধা এবং একটি ডেড লেটার টেবিল—সবই সেই স্থায়িত্বের (durability) ওপর ভিত্তি করে যার জন্য আপনি ইতিমধ্যে মূল্য পরিশোধ করছেন। ব্যাকলগ দেখার কুয়েরিটি হলো: SELECT count(*) FROM job WHERE run_after <= now();
যেখানে এটি কাজ করা বন্ধ করে দেয়। প্রতিটি দাবি (claim) এবং ডিলিট একটি রাইট অপারেশন, তাই উচ্চ হারের জব থাকলে তা ডেড রো ভার্সন রেখে যায় এবং কিউ টেবিলটি এমন একটি ক্লাসিক উদাহরণ যেখানে bloat, autovacuum-এর গতির চেয়ে দ্রুত বেড়ে যায়। দীর্ঘস্থায়ী জবগুলো পরিস্থিতি আরও খারাপ করে, কারণ কাজের সময়জুড়ে খোলা থাকা ট্রানজ্যাকশন পুরো ডেটাবেসের জন্য vacuum horizon-কে আটকে রাখে। পোলিং ল্যাটেন্সি বাড়িয়ে দেয় এবং NOTIFY এর সাথে LISTEN পোলিং দূর করলেও রাইট অপারেশনগুলো কমায় না। যখন জব টেবিলটি আপনার সবচেয়ে ব্যস্ত টেবিল হয়ে ওঠে, অথবা যখন দ্বিতীয় কোনো সার্ভিসের একই ইভেন্টের প্রয়োজন হয়, তখন কাজটিকে সরিয়ে নিন। এই সিদ্ধান্তটি ডেটাবেস কীভাবে ডিপ্লয় করা হয়েছে তার ওপর নির্ভর করে, তাই ডেটাবেসটি Docker-এ নাকি host-এ চলবে তা ব্রোকার যোগ করার আগেই নিশ্চিত করুন।
Redis হলো অন্য একটি টুল যা আপনি হয়তো ইতিমধ্যে ব্যবহার করছেন। Redis Streams আপনাকে XADD এবং XREADGROUP সহ কনজিউমার গ্রুপ, প্রতি গ্রুপের জন্য একটি পেন্ডিং লিস্ট এবং মৃত কনজিউমারের কাছ থেকে কাজ ফিরিয়ে নেওয়ার জন্য XAUTOCLAIM প্রদান করে। এটি ছোট এবং দ্রুত। একটি বক্সে ব্যবহারের ক্ষেত্রে সত্য বিষয়টি হলো: সাধারণ appendfsync everysec সেটিংয়ের কারণে, বিদ্যুৎ বিভ্রাটে প্রায় এক সেকেন্ডের রাইট ডেটা হারিয়ে যেতে পারে। ক্যাশ ইনভ্যালিডেশনের জন্য এটি ঠিক থাকলেও পেমেন্টের মতো কাজের জন্য এটি ভুল। যদি আপনার অ্যাপ্লিকেশনটি VPS-এ প্রোডাকশনে SQLite ব্যবহারের ওপর ভিত্তি করে তৈরি একটি একক প্রসেস হয়, তবে একই claim-and-delete প্যাটার্ন কাজ করবে, যদিও SQLite-এ SKIP LOCKED এর কোনো সমতুল্য ব্যবস্থা নেই এবং প্রতিটি ওয়ার্কারকে একটি মাত্র রাইট লকের ওপর সিরিয়ালাইজ হতে হয়।
NATS core: memory ছাড়া subject routing
docker run -d --name nats \
-p 4222:4222 -p 127.0.0.1:8222:8222 \
nats:2.15 -m 8222October 2026 অনুযায়ী বর্তমান server line হলো 2.15। -m 8222 HTTP monitoring port চালু করে। এটি default হিসেবে বন্ধ থাকে এবং এতে authentication নেই, তাই উপরের মতো localhost-এ bind করুন।
Core NATS সর্বোচ্চ একবার delivery করে এবং কোনো কিছু সংরক্ষণ করে না। একটি publisher orders.created-এর মতো subject-এ publish করে, এবং যার filter মেলে এমন প্রতিটি subscriber একটি করে copy পায়। কোনো subscriber না থাকলে message বাদ পড়ে এবং publisher কোনো error দেখে না, কারণ server bytes গ্রহণ করার সঙ্গে publisher-এর কাজ শেষ হয়। Queue group-এ (একই group name ভাগ করা একাধিক subscriber) server প্রতি message-এর জন্য একটি member বেছে নেয়। এতে queue সংরক্ষণ না করেই কাজ ভাগ করা যায়।
Footprint হলো subscription state এবং প্রতিটি connection-এর জন্য একটি write buffer। তাই এটি message volume-এর বদলে connection count অনুসরণ করে, এবং disk-এ কিছু জমা হয় না। Restart-এর আচরণও এর ফলেই নির্ধারিত হয়: in-flight message হারিয়ে যায়, client নিজে থেকে reconnect করে, এবং অপেক্ষা করার মতো কোনো recovery step থাকে না।
দেখার মতো কোনো backlog নেই, তাই loss-এর লক্ষণ monitor করুন। Server কোনো client-এর socket-এ যত দ্রুত write করে, client তার চেয়ে ধীরে পড়লে সেই client-এর জন্য server-এর buffer পূর্ণ হয়ে যায়। Write deadline শেষ হওয়ার আগে client catch up করতে না পারলে server পুরো connection বন্ধ করে এবং একটি counter বাড়ায়।
curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'একটি slow_consumers value ক্রমাগত বাড়তে থাকলে message বাদ পড়ছে। তাই value একবার পড়ে না দেখে এর ওপর alert সেট করুন। এমন message-এর জন্য Core NATS উপযোগী, যার value দ্রুত expire হয়ে যায়: একটি metric, একটি presence update, অথবা এমন cache invalidation যেটিকে পরবর্তী event যেকোনোভাবে supersede করবে।
NATS JetStream: একই process-এ durable stream এবং replay
JetStream কোনো দ্বিতীয় product নয়। এটি একই binary-এর একটি subsystem, যা একটি flag দিয়ে সক্রিয় করা হয়।
docker run -d --name nats \
-p 4222:4222 -p 127.0.0.1:8222:8222 \
-v nats-data:/data \
nats:2.15 -js -sd /data -m 8222-sd /data store directory নির্ধারণ করে। এটি বাদ দিলে JetStream তার data /tmp-এর অধীনে সংরক্ষণ করে, যার durability নাম শুনলেই যেমন বোঝা যায় তেমনই। CLI দিয়ে একটি stream তৈরি করুন; CLI-টি nats-box image-এর সঙ্গে প্রকাশিত হয়।
docker run --rm -it --network host natsio/nats-box:latest \
nats stream add ORDERS \
--subjects 'orders.>' \
--storage file \
--retention limits \
--max-age 72h \
--max-bytes=1073741824 \
--discard old \
--defaultsছোট server-এ প্রতিটি limit-এরই প্রয়োজন আছে। crash-এর পরেও --storage file টিকে থাকে, কারণ memory stream টিকে থাকে না। --max-bytes=1073741824 byte count হিসেবে stream-এ লেখা data-এর সীমা 1 GiB নির্ধারণ করে, আর সীমায় পৌঁছালে --discard old নতুন write প্রত্যাখ্যান না করে পুরোনো message সরিয়ে দেয়। সীমা নির্ধারণ না করলে একটি নিয়ন্ত্রণহীন publisher disk পূর্ণ করে ফেলতে পারে। তখন database-ও বন্ধ হয়ে যাবে, কারণ উভয়ই একই disk ব্যবহার করে।
একটি durable consumer stream-এ নিজের অবস্থান ধরে রাখে এবং restart-এর পরেও তা বজায় রাখে। consumer-এ --max-deliver নির্ধারণ করুন, যাতে বারবার ব্যর্থ হওয়া কোনো message অনির্দিষ্টকাল redeliver না হয়। কোনো message-এর delivery শেষ হয়ে গেলে JetStream $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>-এ একটি advisory প্রকাশ করে। সেই subject-এ subscribe করেই dead letter path তৈরি করতে হয়, যা RabbitMQ একটি built-in feature হিসেবে দেয়। এই কাজ আপনাকেই লিখে করতে হবে।
backlog দেখতে stored message-এর সংখ্যা পেতে nats stream report চালান। প্রতিটি consumer-এর outstanding acknowledgement এবং unprocessed message দেখতে nats consumer report ORDERS চালান। alarm করার জন্য unprocessed সংখ্যাটিই ব্যবহার করুন। store directory-এর disk usage du -sh দিয়ে দেখা যায়। retention limit তা কমানো পর্যন্ত এটি বাড়তে থাকে।
RabbitMQ: প্রতিটি message acknowledge করুন, ব্যর্থ message আলাদা queue-তে রাখুন
docker run -d --name rabbitmq \
-p 5672:5672 -p 127.0.0.1:15672:15672 \
-v rabbitmq-data:/var/lib/rabbitmq \
rabbitmq:4-managementOctober 2026 অনুযায়ী বর্তমান series হলো 4.3। Port 5672 হলো AMQP (advanced message queuing protocol), আর 15672 হলো management interface। Interface-টি localhost-এ রাখুন এবং SSH tunnel-এর মাধ্যমে ব্যবহার করুন।
x-queue-type argument-টি quorum নির্ধারণ করে queue declare করুন; default এখনও classic। Quorum queue সব সময় durable এবং অন্য কিছু করার আগে disk-এ data লেখে। তাই একটি node-এ durable ও transient option-এর সমন্বয় নিয়ে একাধিক আচরণের পরিবর্তে একটি স্পষ্ট আচরণ পাওয়া যায়। একটি policy দিয়ে dead letter target নির্ধারণ করুন।
docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
'{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
--apply-to queues --priority 7চারটি কারণে কোনো message dead letter হয়: কোনো consumer basic.reject বা basic.nack দিয়ে সেটিকে reject করে এবং requeue false থাকে; message-এর per-message TTL (time to live) শেষ হয়; queue length limit অতিক্রম করে; অথবা message quorum queue delivery limit ছাড়িয়ে যায়। RabbitMQ 4.0 থেকে ওই limit-এর default হলো 20। তাই কোনো handler exception ছুড়ে nack করলে message 20 বার retry হয়, এরপর loop না করে dead letter exchange-এ পাঠানো হয়।
ছোট VPS-এ RabbitMQ-এর আচরণে সবচেয়ে বেশি বিস্ময় দেখা যায় memory ব্যবহারে। Default high watermark হলো available RAM-এর 0.6। Node এই সীমা অতিক্রম করলে publishing করা প্রতিটি connection RabbitMQ block করে। আপনার application কোনো error পায় না। এমন publish পায় যা কখনও return করে না। আপনার code-এ এটি hang হিসেবে দেখা যায়। Startup log-এ node যে সংখ্যা হিসাব করেছে তা লেখা থাকে:
Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) totalFree space default 50 MB-এর নিচে নেমে গেলেও disk alarm একইভাবে publisher block করে। Quorum queue-এর ক্ষেত্রে এর সঙ্গে অতিরিক্ত memory হিসাব যুক্ত হয়। Documentation অনুযায়ী প্রতি message-এর in-memory metadata-এর জন্য অন্তত 32 bytes, 30,000 message-এর জন্য প্রায় 1 MB, এবং effective write-ahead log size-এর অন্তত তিন গুণ RAM রাখা উচিত। WAL limit-এর default হলো 512 MiB। তাই শুধু এই recommendation-এর জন্যই 1.5 GB RAM প্রয়োজন হয়। 2 GB server-এ default যথেষ্ট হবে ধরে না নিয়ে rabbitmq.conf-এ limit কমিয়ে দিন।
raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5Backlog দুটি সংখ্যা নিয়ে গঠিত। এই জোড়া সংখ্যা দেখেই বোঝা যায় কোন ধরনের failure ঘটেছে।
docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledgedmessages_ready হলো consumer-এর অপেক্ষায় থাকা message। messages_unacknowledged deliver করা হয়েছে, কিন্তু কখনও ack করা হয়নি। Ready count স্থির থাকা অবস্থায় unacknowledged count বাড়তে থাকলে বুঝবেন worker-গুলো job নিয়েছে, কিন্তু সেগুলো শেষ করা বন্ধ করেছে। এটি কেবল পিছিয়ে থাকা queue-এর চেয়ে ভিন্ন ধরনের bug।
একটি মেশিনে Kafka, এবং কোথায় এটি আর যুক্তিসঙ্গত থাকে না
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
bin/kafka-storage.sh format --standalone -t $KAFKA_CLUSTER_ID -c config/server.properties
bin/kafka-server-start.sh config/server.propertiesএটি Kafka 4.3.1-এর quickstart; October 2026 অনুযায়ী এটি KRaft mode-এ চলে। KRaft-এর পূর্ণরূপ Kafka Raft। এটি Kafka 4.0-এ ZooKeeper-এর পরিবর্তে ব্যবহৃত built-in controller। Container-এর সমতুল্য হলো apache/kafka:4.3.1।
আপনি নিজে সেট না করলে start script export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" সেট করে। ফলে broker একটি message সংরক্ষণ করার আগেই 1 GB Java heap reserve করে। এটি যে page cache থেকে পড়ে, তার জন্যও অতিরিক্ত free RAM প্রয়োজন। 2 GB VPS-এ বাকি RAM-এর জন্য আপনার application-কে JVM-এর সঙ্গে প্রতিযোগিতা করতে হয়।
Retention হলো পরের বিস্ময়। log.retention.hours-এর default মান 168, অর্থাৎ seven days। log.retention.bytes-এর default মান -1, অর্থাৎ কোনো size limit নেই। Kafka পুরো retention window জুড়ে message সংরক্ষণ করে, প্রতিটি consumer সেগুলো পড়েছে কি না তা বিবেচনা না করেই। আপনি যে feature-এর জন্য Kafka বেছে নিয়েছেন, এটিই সেই feature। তবে একটি ছোট disk-এ এটিই failure mode হতে পারে। তাই বিষয়টি বোঝার আগেই প্রতি topic-এর জন্য byte limit সেট করুন।
এখন বাস্তব দিকটি দেখা যাক। একটি broker থাকলে replication factor 1 হয়। তাই acks=all একটি disk-এ একটি fsync-এ পরিণত হয়। আপনি একটি machine-এর durability পান, কিন্তু JVM broker এবং controller চালানোর operating cost-সহ। আপনার কাছে নেই এমন broker-গুলোর মধ্যে partitions parallelism দেয়। Replication, rack awareness এবং fleet-এর অন্যান্য feature নিষ্ক্রিয় অবস্থায় থাকে। একই box-এ JetStream অনেক কম memory ব্যবহার করে একই durable replay দিতে পারে। তবু এখানে Kafka ব্যবহারের দুটি যুক্তি আছে: downstream কোনো tool শুধু Kafka protocol সমর্থন করে, যেমন Debezium দিয়ে change data capture বা কোনো analytics loader; অথবা আপনি production topology-এর একটি ছোট সংস্করণ তৈরি করছেন। Cluster বড় করার পরিকল্পনা মানে পরে আরও machine কেনার পরিকল্পনা। ততদিন পর্যন্ত এই trade-off-টি একটি node-এ k3s চালানোর মতোই, যেখানে একটি node-এর reliability-এর জন্য cluster complexity বহন করতে হয়।
Kafka-তে backlog-কে consumer lag বলা হয়।
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-groupLAG column পড়ুন। প্রতিটি partition-এর জন্য এটি LOG-END-OFFSET থেকে CURRENT-OFFSET বাদ দিলে যে মান পাওয়া যায়। অন্য partition-গুলো স্থির থাকা অবস্থায় একটি partition-এ lag বাড়তে থাকলে uneven key-এর সম্ভাবনা থাকে। একই key-সহ সব message একই partition-এ যায়, এবং সেগুলো একাই একটি consumer process করে।
বক্স রিস্টার্ট হলে কী ঘটে
Core NATS চলাকালীন সমস্ত ডেটা হারিয়ে ফেলে এবং তাৎক্ষণিকভাবে পুনরায় চালু হয়, কারণ পুনরুদ্ধার করার মতো কিছু থাকে না। JetStream স্টোর ডিরেক্টরি থেকে স্ট্রিম এবং কনজিউমার পজিশন পুনরায় লোড করে, তাই কনজিউমাররা তাদের পূর্বের অফসেট থেকেই কাজ শুরু করতে পারে। RabbitMQ ডিস্ক থেকে কোরাম কিউ (quorum queues) পুনরুদ্ধার করে, কিন্তু ক্লাসিক ট্রানজিয়েন্ট কিউ এবং পারসিস্টেন্ট ডেলিভারি মোড ছাড়া প্রকাশিত যেকোনো মেসেজ মুছে যায়। Kafka স্টার্টআপের সময় তার লগ সেগমেন্টগুলো পুনরায় প্লে করে; অস্বাভাবিক শাটডাউনের পর ব্রোকার কানেকশন গ্রহণ করার আগে ছোট ডিস্কেও এই রিকভারি স্ক্যান সম্পন্ন হতে কয়েক মিনিট সময় নিতে পারে।
দুটি বিষয় একবার সেট করে রাখা ভালো। কন্টেইনারের জন্য একটি রিস্টার্ট পলিসি (restart: unless-stopped) দিন অথবা systemd ইউনিট সক্রিয় করুন, যাতে কার্নেল আপগ্রেডের পর রিবুট হলেও ব্রোকার আপনার হস্তক্ষেপ ছাড়াই ফিরে আসে। এরপর ক্রম বা অর্ডারটি সামলান: আপনার অ্যাপ্লিকেশনের বিশ সেকেন্ড পরে ব্রোকার রেডি হলে তা প্রথম কানেকশনগুলো প্রত্যাখ্যান করবে এবং কিছু ক্লায়েন্ট লাইব্রেরি পুনরায় চেষ্টা করার পরিবর্তে বন্ধ হয়ে যায়। Compose healthchecks that hold a dependent service back until the broker is ready ব্যবহার করে ব্রোকার রেডি না হওয়া পর্যন্ত অ্যাপটিকে আটকে রাখুন।
আপনার নিজস্ব VPS-এ খরচ, অনুমানের পরিবর্তে পরিমাপ করুন
প্রকাশিত থ্রুপুট (throughput) পরিসংখ্যান এমন হার্ডওয়্যারে পরিমাপ করা হয় যা আপনার কাছে নেই, সাধারণত লোকাল NVMe সহ মাল্টি-কোর সার্ভারে। এগুলোকে সর্বোচ্চ সীমা হিসেবে বিবেচনা করুন এবং আপনার নিজস্ব সার্ভারে পরিমাপ করুন।
docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_dataব্রোকার অলস (idle) থাকা অবস্থায় এবং আপনার প্রকৃত ট্রাফিকের অধীনে এগুলো চালান। এই দুটির মধ্যে পার্থক্যই নির্ধারণ করবে যে ব্রোকারটি আপনার অ্যাপ্লিকেশনের পাশে চলবে কি না। থ্রুপুটের একটি মোটামুটি সর্বনিম্ন সীমা পেতে, অন্য কারো ব্লগ পোস্টের পরিবর্তে প্রতিটি প্রজেক্টের নিজস্ব লোড জেনারেটর ব্যবহার করুন: NATS-এর জন্য nats bench pub test --msgs 100000 --clients 2, Kafka-এর জন্য bin/kafka-producer-perf-test.sh এবং RabbitMQ-এর জন্য PerfTest। একই VPS-এ জেনারেটর চালানো ব্রোকার এবং জেনারেটর উভয়কেই একসাথে পরিমাপ করে, যা গ্রহণযোগ্য, যদি আপনি পরিসংখ্যান রিপোর্ট করার সময় তা উল্লেখ করেন।
একটি সর্বোচ্চ সীমা সবার ক্ষেত্রেই প্রযোজ্য। এখানে প্রতিটি টেকসই (durable) অপশন fsync-এর জন্য অপেক্ষা করে, তাই নেটওয়ার্ক-সংযুক্ত স্টোরেজ (network-attached storage) বিশিষ্ট VPS-এ ডিস্কই সীমাবদ্ধতা নির্ধারণ করে, এবং ব্রোকার পরিবর্তন করলে এই সীমা বাড়বে না।
তিনটি ওয়ার্কলোড এবং প্রতিটি যে মেসেজ কিউ চায়
- একটি ওয়েব অ্যাপ্লিকেশনের জন্য ব্যাকগ্রাউন্ড জব, যেমন ইমেইল পাঠানো, ইমেজ রিসাইজ করা বা ওয়েবহুক ডেলিভারি করা। Postgres এবং
SKIP LOCKEDদিয়ে শুরু করুন। যখন আপনার প্রতি-মেসেজ একনলেজমেন্ট (acks), ডেলিভারি লিমিট এবং এমন একটি ডেড লেটার কিউ প্রয়োজন হয় যা নিজে লজিক না লিখেও পরীক্ষা করা যায়, অথবা যখন জব টেবিলটি ডাটাবেসের সবচেয়ে ব্যস্ত টেবিলে পরিণত হয়, তখন RabbitMQ এবং কোরাম কিউ (quorum queues)-তে চলে যান। - এমন ইভেন্ট যা একাধিক অভ্যন্তরীণ সার্ভিস গ্রহণ করে, যেখানে কোনো মেসেজ হারিয়ে গেলে দ্রুত নতুন একটি মেসেজ দিয়ে তা প্রতিস্থাপন করা যায়। এক্ষেত্রে Core NATS ব্যবহার করুন, যেখানে রাউটিং স্কিম হিসেবে সাবজেক্ট এবং কাজের ভাগাভাগির জন্য কিউ গ্রুপ ব্যবহার করা হয়। যে অল্প সংখ্যক সাবজেক্ট রিস্টার্টের পরেও টিকে থাকা প্রয়োজন, সেগুলোর জন্য JetStream স্ট্রিম যোগ করুন এবং বাকিগুলো মেমরিতে রাখুন।
- একটি ইভেন্ট লগ যা কনজিউমাররা শুরু থেকে পড়ে, অডিট ট্রেইল রাখা, রিড মডেল পুনর্গঠন বা পরবর্তীতে অ্যানালিটিক্সের জন্য। ফাইল স্টোরেজ এবং নির্দিষ্ট বাইট ক্যাপসহ JetStream ব্যবহার করুন। শুধুমাত্র তখনই Kafka বেছে নিন যখন কোনো ডাউনস্ট্রিম টুলের Kafka প্রোটোকল প্রয়োজন হয়, এবং সেই সামঞ্জস্যতার মূল্য হিসেবে JVM হিপ ব্যবহারের বিষয়টি মেনে নিন।
একটি সিঙ্গেল সার্ভারে ভুল নির্বাচনের মাশুল থ্রুপুট নয়। এর আসল মাশুল হলো রাত তিনটার সময় রিকভারি করা, যখন আপনাকে নিশ্চিত হতে হয় যে মেসেজগুলো আদৌ টিকে আছে কি না। সেই পরিস্থিতির কথা মাথায় রেখেই নির্বাচন করুন।
FAQ
আমি কি 2 GB VPS-এ Kafka চালাতে পারি?
এটি চালু হবে, তবে বেশ চাপের মুখে থাকবে। আপনি যদি ওভাররাইড না করেন তবে bin/kafka-server-start.sh ডিফল্টভাবে KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" সেট করে, তাই কোনো মেসেজ জমা করার আগেই JVM 1 GB মেমোরি দখল করে নেয় এবং Kafka এর বাইরে পেজ ক্যাশের জন্য খালি মেমোরির ওপর নির্ভর করে। একই সার্ভারে আপনার অ্যাপ্লিকেশন এবং ডাটাবেস যোগ করলে তা সোয়াপ (swap) মেমোরি ব্যবহার করতে বাধ্য হবে। এছাড়া আপনি রেপ্লিকেশন ফ্যাক্টর 1 ব্যবহার করবেন, যার মানে acks=all হলো একটি ডিস্কে একটি fsync, অর্থাৎ আপনি Kafka-এর স্থায়িত্বের মডেল ছাড়াই এর পরিচালনার খরচ বহন করছেন। একই হার্ডওয়্যারে NATS JetStream অনেক কম মেমোরিতে টেকসই রিপ্লে (durable replay) সুবিধা দেয়।
আমার যদি ইতিমধ্যে Postgres থাকে, তবে কি আমার মেসেজ কিউ (message queue) প্রয়োজন?
সাধারণত প্রয়োজন নেই। একটি job টেবিল থেকে SELECT ... FOR UPDATE SKIP LOCKED ব্যবহার করে ট্রানজেকশনের মাধ্যমে রিড করলে আপনি 'এট-লিস্ট-ওয়ানস' (at-least-once) ডেলিভারি, নিরাপদ কনকারেন্ট ওয়ার্কার, রিট্রাই এবং ডেড লেটার টেবিল সুবিধা পাবেন, যার জন্য বাড়তি কোনো সার্ভিস মনিটর করতে হবে না এবং আপনার বর্তমান ব্যাকআপ পদ্ধতিই যথেষ্ট। কিউ থেকে বেরিয়ে আসার সংকেতগুলো হলো: কিউ টেবিলটি যদি আপনার সবচেয়ে ভারী রাইট লোড হয়ে দাঁড়ায় এবং অটোভ্যাকিউম (autovacuum) পিছিয়ে পড়ে, দীর্ঘস্থায়ী কাজগুলো যদি ট্রানজেকশন ওপেন রেখে পুরো ডাটাবেসের ভ্যাকুয়াম আটকে দেয়, অথবা যদি দ্বিতীয় কোনো সার্ভিসের স্বাধীনভাবে একই ইভেন্টগুলো কনজিউম করার প্রয়োজন হয়।
ব্যাকগ্রাউন্ড জবের জন্য আমার কি NATS JetStream নাকি RabbitMQ ব্যবহার করা উচিত?
যদি আপনি প্রতি-মেসেজ অ্যাকনলেজমেন্ট (per-message acknowledgement), ডেলিভারি লিমিট এবং বিল্ট-ইন ডেড লেটার রাউটিং চান, তবে RabbitMQ ব্যবহার করুন। কোরাম কিউ (Quorum queues) সবসময় টেকসই হয়, RabbitMQ 4.0 থেকে ডেলিভারি লিমিট ডিফল্টভাবে 20 সেট করা থাকে এবং একটি পলিসির মাধ্যমে ব্যর্থ মেসেজগুলোকে ডেড লেটার এক্সচেঞ্জে পাঠানো যায়, যা আপনি পরে পরীক্ষা করতে পারবেন। যদি একই ইভেন্টগুলো পরে অন্য কনজিউমারদের রিপ্লে করার প্রয়োজন হয়, তবে JetStream ব্যবহার করুন, কারণ কিউ মেসেজ ডিলিট করে দিলেও স্ট্রিম অ্যাকনলেজমেন্টের পরেও মেসেজগুলো রেখে দেয়। JetStream-এ আপনি --max-deliver সেট করতে পারেন এবং $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> অ্যাডভাইজরি থেকে ডেড লেটার পাথ নিজে তৈরি করতে পারেন।
আমার কনজিউমাররা কতটা পিছিয়ে আছে তা কীভাবে বুঝব?
প্রতিটি ব্রোকারের জন্য একটি নির্দিষ্ট কমান্ড রয়েছে। RabbitMQ-এর ক্ষেত্রে, rabbitmqctl list_queues name messages messages_ready messages_unacknowledged কনজিউমারের জন্য অপেক্ষমাণ কাজ এবং ডেলিভারি হওয়া কিন্তু অ্যাকনলেজ না হওয়া কাজের মধ্যে পার্থক্য দেখায়। JetStream-এর ক্ষেত্রে, nats consumer report <stream> প্রতি কনজিউমারে প্রসেস না হওয়া মেসেজ এবং বকেয়া অ্যাকনলেজমেন্ট দেখায়। Kafka-এর ক্ষেত্রে, kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> প্রতি পার্টিশনে একটি LAG কলাম প্রিন্ট করে। কোর NATS-এ পড়ার মতো কোনো ব্যাকলগ নেই, কারণ এটি কিছুই সংরক্ষণ করে না; এর পরিবর্তে http://localhost:8222/varz-এ slow_consumers কাউন্টারটি দেখুন: এটি সেই সংযোগগুলোর সংখ্যা দেখায় যা সার্ভার পিছিয়ে পড়ার কারণে বন্ধ করে দিয়েছে, যার অর্থ হলো মেসেজ লস।