SSD Nodes Learn 🎉 VPS $5.50/ماہ سے
تعلیمی Matt Connorتحریر: Matt Connor

ایک VPS پر NATS، RabbitMQ یا Kafka؟

ایک VPS پر درست message queue چنیں: delivery guarantees، memory اور disk cost، restart behaviour، backlog checks، اور وہ حالات جانیں جب Postgres بہتر انتخاب ہے۔

ایک سرور کے لیے مختصر جواب

ایک VPS پر message queue کا انتخاب رفتار سے زیادہ delivery guarantees کا فیصلہ ہے۔ ایک ہی machine پر broker عموماً bottleneck نہیں بنتا، کیونکہ آپ کا application code، database اور واحد disk پہلے ہی محدودیت پیدا کر دیتے ہیں۔ ایسے tool کا انتخاب کریں جس کے failure behaviour کو آپ قبول کر سکتے ہوں، پھر اسی machine کی پیمائش کریں جو آپ کے پاس موجود ہے۔

یہ چار options اس ترتیب سے ہیں جس پر زیادہ تر readers کو غور کرنا چاہیے۔

  • پہلے سے چلنے والے database کو استعمال کریں۔ Postgres میں SELECT ... FOR UPDATE SKIP LOCKED ایک قابلِ عمل job queue ہے، اور اس سے monitor کرنے کے لیے کوئی نیا process شامل نہیں ہوتا۔
  • RabbitMQ اس وقت استعمال کریں جب ہر message ایک ایسا work unit ہو جسے acknowledge کرنا ضروری ہو، محدود تعداد میں retry کیا جائے، اور پھر ایسی جگہ park کر دیا جائے جہاں کوئی انسان اسے دیکھ سکے۔
  • NATS اس وقت استعمال کریں جب messages events ہوں جن پر آپ کے system کے کئی حصے ردِعمل دیتے ہوں۔ جن events کو restart کے بعد بھی محفوظ رہنا ضروری ہو، ان کے لیے JetStream فعال کریں۔
  • Kafka اس وقت استعمال کریں جب downstream tool صرف Kafka protocol استعمال کرتا ہو۔ ایک سرور پر یہ تقریباً واحد باقی رہ جانے والی وجہ ہے۔

اس guide کے باقی حصے میں اس انتخاب کی وجوہات بیان کی گئی ہیں: چھوٹے VPS پر ہر option کتنی memory اور disk استعمال کرتا ہے، machine reboot ہونے پر اس کا behaviour کیا ہوتا ہے، اور وہ exact command کون سی ہے جو users کو اثر محسوس ہونے سے پہلے backlog دکھا دیتی ہے۔

ڈیلیوری کی ضمانت کا حقیقی مطلب

زیادہ سے زیادہ ایک بار کا مطلب ہے کہ broker پیغام consumer کے حوالے کر کے اسے بھول جاتا ہے۔ اگر کوئی consumer منسلک نہ ہو، یا consumer کام مکمل ہونے سے پہلے بند ہو جائے، تو پیغام ضائع ہو جاتا ہے اور کسی جگہ اس کی اطلاع درج نہیں ہوتی۔

کم از کم ایک بار کا مطلب ہے کہ کام کامیاب ہونے کے بعد consumer acknowledgement (ack) بھیجتا ہے۔ یہ ack موصول ہونے تک broker پیغام محفوظ رکھتا ہے اور اسے دوبارہ deliver کرے گا۔ Redelivery کی وجہ سے آپ کے handlers کا idempotent ہونا ضروری ہے: ایک ہی پیغام کو دو بار process کرنے سے card پر دو بار charge نہیں ہونا چاہیے۔ End to end exactly once ایسی ضمانت نہیں جو broker فراہم کرتا ہو۔ یہ آپ کے اپنے database میں unique key سے حاصل ہوتی ہے۔

Replay ایک الگ خصوصیت ہے۔ Queue کسی پیغام کے acknowledged ہونے کے بعد اسے حذف کر دیتی ہے۔ Log اسے retention window تک محفوظ رکھتا ہے، اس لیے نیا consumer beginning سے شروع کر کے پوری history پڑھ سکتا ہے۔ Kafka اور NATS JetStream logs ہیں۔ RabbitMQ queue ہے۔ یہ فرق throughput سے زیادہ architectures کی ساخت متعین کرتا ہے۔

Dead lettering اس وقت ہوتا ہے جب کوئی پیغام بار بار fail ہوتا رہے۔ اس کے بغیر poison message ہمیشہ loop میں رہتا ہے، اور یہ loop worker کے broken ہونے کے بجائے مصروف ہونے جیسا دکھائی دیتا ہے۔

Postgres سے شروع کریں اور broker کو اپنی افادیت ثابت کرنے دیں

زیادہ تر single-application workloads میں روزانہ چند ہزار background jobs ہوتی ہیں۔ یہ مقدار ایک table میں سنبھالی جا سکتی ہے۔

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);

ایک worker transaction کے اندر ایک job حاصل کرتا ہے۔

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 پوری ترکیب ہے۔ یہ واپس کی جانے والی row کو lock کرتا ہے اور ایسی ہر row کو چھوڑ دیتا ہے جسے کسی دوسری transaction نے پہلے ہی lock کیا ہو، اس لیے دو workers کبھی ایک ہی job حاصل نہیں کرتے۔ اگر کوئی worker crash ہو جائے تو Postgres اس کی transaction abort کر دیتا ہے، lock release ہو جاتا ہے، اور row اگلے worker کو نظر آنے لگتی ہے۔ آپ کو at-least-once delivery، attempts کی قدر بڑھا کر retries، اور dead letter table ملتے ہیں، اور یہ سب اس durability پر چلتا ہے جس کی لاگت آپ پہلے ہی ادا کر رہے ہیں۔ backlog معلوم کرنے کے لیے ایک query کافی ہے: SELECT count(*) FROM job WHERE run_after <= now();

یہ طریقہ کہاں ناکام ہونے لگتا ہے۔ ہر claim اور delete ایک write ہے، اس لیے jobs کی شرح زیادہ ہونے پر پیچھے dead row versions رہ جاتے ہیں، اور queue table وہ معروف صورت ہے جس میں bloat، autovacuum سے زیادہ تیزی سے بڑھتا ہے۔ Long jobs مسئلہ مزید بڑھا دیتی ہیں، کیونکہ کام کے پورے دورانیے تک کھلی رہنے والی transaction پورے database کے vacuum horizon کو بھی روک کر رکھتی ہے۔ Polling latency بڑھاتی ہے، اور LISTEN کو NOTIFY کے ساتھ استعمال کرنے سے polling ختم ہو جاتی ہے، مگر writes ختم نہیں ہوتیں۔ جب job table آپ کے پاس موجود سب سے زیادہ مصروف table بن جائے، یا کسی دوسری service کو بھی یہی events درکار ہوں، تو کام کو باہر منتقل کریں۔ یہ فیصلہ اس بات سے بھی جڑا ہے کہ database خود کیسے deploy کیا گیا ہے، اس لیے broker کو اس کے ساتھ شامل کرنے سے پہلے یہ طے کریں کہ database Docker میں چلتا ہے یا host پر۔

Redis وہ دوسری چیز ہے جو ممکن ہے آپ پہلے ہی چلا رہے ہوں۔ Redis Streams، XADD اور XREADGROUP کے ساتھ consumer groups، ہر group کے لیے pending list، اور XAUTOCLAIM فراہم کرتے ہیں تاکہ مر جانے والے consumer سے کام واپس لیا جا سکے۔ یہ چھوٹا اور تیز ہے۔ ایک ہی box پر اس کی اہم حد یہ ہے کہ عام appendfsync everysec setting کے ساتھ power loss سے تقریباً ایک second کی writes ضائع ہو سکتی ہیں۔ یہ cache invalidation کے لیے قابل قبول ہے، مگر payments کے لیے غلط ہے۔ اگر آپ کی application ایک single process ہے اور production میں VPS پر SQLite کے گرد بنائی گئی ہے، تو یہی claim-and-delete pattern کام کرتا ہے، اگرچہ SQLite میں SKIP LOCKED کے برابر کوئی سہولت نہیں اور ہر worker ایک ہی write lock پر باری کا انتظار کرتا ہے۔

NATS core: subject routing with no memory

docker run -d --name nats \
  -p 4222:4222 -p 127.0.0.1:8222:8222 \
  nats:2.14 -m 8222

August 2026 تک موجودہ server line 2.14 ہے۔ -m 8222 HTTP monitoring port فعال کرتا ہے۔ یہ بطور default بند ہوتا ہے اور اس میں authentication نہیں ہوتی، اس لیے اسے اوپر بیان کیے گئے طریقے کے مطابق localhost سے bind کریں۔

Core NATS زیادہ سے زیادہ ایک بار پیغام پہنچاتا ہے اور کچھ بھی محفوظ نہیں کرتا۔ Publisher کسی subject، مثلاً orders.created، کو پیغام بھیجتا ہے، اور ہر وہ subscriber جس کا filter match کرتا ہے، پیغام کی ایک copy حاصل کرتا ہے۔ اگر کوئی subscriber موجود نہ ہو تو پیغام drop ہو جاتا ہے اور publisher کو کوئی error نہیں ملتا، کیونکہ server کے bytes قبول کرتے ہی publisher کا کام مکمل ہو جاتا ہے۔ Queue group میں متعدد subscribers ایک ہی group name استعمال کرتے ہیں۔ ایسی صورت میں server ہر پیغام کے لیے ایک member منتخب کرتا ہے۔ اس سے پیغامات محفوظ کیے بغیر کام تقسیم ہو جاتا ہے۔

Memory footprint میں subscription state اور ہر connection کے لیے write buffer شامل ہوتا ہے۔ اس لیے اس کا تعلق message volume کے بجائے connection count سے ہوتا ہے، اور disk پر کچھ بھی جمع نہیں ہوتا۔ Restart کا رویہ بھی اسی کا نتیجہ ہے: in-flight messages ضائع ہو جاتے ہیں، clients خود reconnect کرتے ہیں، اور recovery کے لیے انتظار کرنے کا کوئی مرحلہ نہیں ہوتا۔

دیکھنے کے لیے کوئی backlog موجود نہیں ہوتا، اس لیے loss کو monitor کریں۔ جب subscriber اپنے socket کو server کی write رفتار سے کم رفتار پر پڑھتا ہے تو اس client کا server-side buffer بھر جاتا ہے۔ اگر client write deadline تک پیچھے رہ جائے تو server پورا connection بند کر دیتا ہے اور ایک counter میں اضافہ کرتا ہے۔

curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'

slow_consumers کی قدر مسلسل بڑھ رہی ہو تو اس کا مطلب ہے کہ messages drop ہو رہے ہیں۔ اس لیے اسے صرف ایک بار پڑھنے کے بجائے اس پر alert لگائیں۔ Core NATS ایسے پیغام کے لیے موزوں ہے جس کی اہمیت جلد ختم ہو جاتی ہے: مثلاً metric، presence update، یا cache invalidation جسے اگلا event ویسے بھی supersede کر دے گا۔

NATS JetStream: اسی process میں durable streams اور 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.14 -js -sd /data -m 8222

-sd /data store directory مقرر کرتا ہے۔ اسے چھوڑ دیں تو JetStream اپنا data /tmp کے تحت محفوظ کرتا ہے، جو نام کے مطابق ہی اتنا durable ہے۔ 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 کے بعد وہی data محفوظ رہتا ہے جو --storage file کے ذریعے disk پر لکھا گیا ہو، کیونکہ memory stream محفوظ نہیں رہتی۔ --max-bytes=1073741824 stream کو byte count کے طور پر 1 GiB تک محدود کرتا ہے، اور --discard old limit پوری ہونے پر نئے writes مسترد کرنے کے بجائے قدیم messages حذف کرتا ہے۔ Limit نہ لگائیں تو ایک بے قابو publisher disk بھر دے گا۔ اس صورت میں database بھی رک جائے گا، کیونکہ دونوں ایک ہی disk استعمال کرتے ہیں۔

durable consumer stream میں اپنی position برقرار رکھتا ہے اور restart کے بعد بھی اسے محفوظ رکھتا ہے۔ Consumer پر --max-deliver مقرر کریں تاکہ مسلسل fail ہونے والا message ہمیشہ دوبارہ deliver نہ ہوتا رہے۔ جب کسی message کی delivery limit ختم ہو جائے تو JetStream $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> پر ایک advisory publish کرتا ہے۔ اس subject کو subscribe کرنا وہ dead letter path بنانے کا طریقہ ہے جو RabbitMQ آپ کو built-in feature کے طور پر دیتا ہے۔ یہ کام آپ کو خود کرنا ہوگا۔

Backlog دیکھنے کے لیے stored message counts کے لیے nats stream report چلائیں، اور ہر consumer کے outstanding acknowledgements اور unprocessed messages کے لیے nats consumer report ORDERS چلائیں۔ Alarm کے لیے unprocessed number استعمال کریں۔ Store directory کے خلاف du -sh سے disk usage دیکھ سکتے ہیں۔ Retention limit اسے trim کرنے تک یہ بڑھتا رہتا ہے۔

RabbitMQ: ہر پیغام کو acknowledge کریں، ناکام پیغامات کو الگ رکھیں

docker run -d --name rabbitmq \
  -p 5672:5672 -p 127.0.0.1:15672:15672 \
  -v rabbitmq-data:/var/lib/rabbitmq \
  rabbitmq:4-management

August 2026 تک موجودہ series 4.3 ہے۔ Port 5672، AMQP (advanced message queuing protocol) کے لیے ہے، جبکہ 15672 management interface کے لیے ہے۔ Interface کو localhost پر رکھیں اور SSH tunnel کے ذریعے اس تک رسائی حاصل کریں۔

Queues کو x-queue-type argument کو quorum پر set کر کے declare کریں؛ default اب بھی classic ہے۔ Quorum queues ہمیشہ durable ہوتی ہیں اور کوئی دوسرا کام کرنے سے پہلے data کو disk پر لکھتی ہیں۔ اس لیے ایک node پر durable اور transient options کے مختلف امتزاج کے بجائے ایک واضح رویہ ملتا ہے۔ Dead letter target کو policy کے ذریعے set کریں۔

docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
  '{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
  --apply-to queues --priority 7

کسی پیغام کو چار وجوہات کی بنا پر dead letter کیا جاتا ہے: consumer اسے basic.reject یا basic.nack کے ساتھ reject کرتا ہے اور requeue false پر set ہوتا ہے، اس کا per-message TTL (time to live) ختم ہو جاتا ہے، queue length limit سے تجاوز کرتی ہے، یا پیغام quorum queue کی delivery limit سے بڑھ جاتا ہے۔ RabbitMQ 4.0 کے بعد یہ limit default طور پر 20 ہے۔ اس لیے ایسا handler جو exception throw کرے اور nack بھیجے، بیس مرتبہ retry کرنے کے بعد پیغام کو loop میں رکھنے کے بجائے dead letter exchange کے حوالے کر دیتا ہے۔

چھوٹے VPS پر memory وہ معاملہ ہے جہاں RabbitMQ اکثر غیر متوقع رویہ دکھاتا ہے۔ Default high watermark دستیاب RAM کا 0.6 ہے۔ جب node اس حد سے تجاوز کرتا ہے تو RabbitMQ publishing کرنے والے ہر connection کو block کر دیتا ہے۔ آپ کی application کو error موصول نہیں ہوتا۔ اسے ایسا publish موصول ہوتا ہے جو کبھی return نہیں ہوتا، اور آپ کے اپنے code میں یہ hang کے طور پر ظاہر ہوتا ہے۔ Startup log میں node کے حساب سے نکالی گئی قدر درج ہوتی ہے:

Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) total

جب free space default طور پر 50 MB سے کم ہو جاتی ہے تو disk alarm بھی publishers کو اسی طرح block کر دیتا ہے۔ Quorum queues اس کے علاوہ اپنا حساب بھی شامل کرتی ہیں: documentation ہر message کے لیے کم از کم 32 bytes in-memory metadata مختص کرنے کی ہدایت دیتی ہے، جو تقریباً 30,000 messages کے لیے 1 MB بنتا ہے، اور RAM میں effective write-ahead log size کے کم از کم تین گنا کی سفارش کرتی ہے۔ WAL limit default طور پر 512 MiB ہے، اس لیے صرف یہ سفارش 1.5 GB RAM کا تقاضا کرتی ہے۔ 2 GB کے server پر default پر انحصار کرنے کے بجائے اسے rabbitmq.conf میں کم کریں۔

raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5

Backlog دو اعداد پر مشتمل ہوتا ہے، اور یہ دونوں مل کر بتاتے ہیں کہ آپ کو کس قسم کی failure درپیش ہے۔

docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledged

messages_ready consumer کا انتظار کر رہا ہے۔ messages_unacknowledged deliver ہو چکا ہے لیکن اس کا ack نہیں ہوا۔ Unacknowledged count کا بڑھنا اور ready count کا تقریباً مستقل رہنا اس بات کی علامت ہے کہ آپ کے workers نے jobs لے لیں اور انہیں مکمل کرنا بند کر دیا۔ یہ اس 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 ہے، جو August 2026 تک موجودہ ہے اور KRaft mode (Kafka Raft، یعنی built-in controller جس نے Kafka 4.0 میں ZooKeeper کی جگہ لی) میں چلتا ہے۔ Container کے مساوی طریقہ کار apache/kafka:4.3.1 ہے۔

جب آپ نے export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" خود set نہ کیا ہو تو start script اسے set کر دیتا ہے۔ اس طرح broker ایک بھی message محفوظ کرنے سے پہلے 1 GB Java heap reserve کرتا ہے، اور page cache کے لیے اس کے علاوہ free RAM بھی درکار ہوتی ہے۔ 2 GB VPS پر آپ کی application باقی دستیاب memory کے لیے JVM سے مقابلہ کرتی ہے۔

Retention اگلا غیر متوقع مسئلہ ہے۔ log.retention.hours کی default value 168 ہے، یعنی سات دن، اور log.retention.bytes کی default value -1 ہے، یعنی size کی کوئی حد نہیں۔ Kafka پورے retention window میں messages محفوظ رکھتا ہے، چاہے ہر consumer نے انہیں پڑھا ہو یا نہیں۔ یہی وہ feature ہے جس کے لیے آپ Kafka لاتے ہیں، لیکن ایک چھوٹی disk پر یہی failure mode بھی بن جاتا ہے۔ اس لیے مسئلہ سامنے آنے سے پہلے ہر topic کے لیے byte limit set کریں۔

اب حقیقت پسندانہ جائزہ۔ Single broker کا مطلب replication factor 1 ہے، اس لیے acks=all ایک disk پر ایک fsync تک محدود رہتا ہے۔ آپ کو ایک machine کی durability ملتی ہے، جبکہ JVM broker اور controller چلانے کی operating cost بھی برقرار رہتی ہے۔ Partitions ایسے brokers کے درمیان parallelism فراہم کرتے ہیں جو آپ کے پاس موجود ہی نہیں۔ Replication، rack awareness اور باقی fleet features غیر فعال رہتے ہیں۔ اسی machine پر JetStream کم memory کے ساتھ durable replay فراہم کرتا ہے۔ یہاں Kafka کے استعمال کو اب بھی دو وجوہات درست ثابت کر سکتی ہیں: downstream tool صرف Kafka protocol استعمال کرتا ہو (مثلاً Debezium کے ذریعے change data capture یا analytics loader)، یا آپ production topology کو چھوٹے پیمانے پر دوبارہ بنا رہے ہوں۔ Cluster میں توسیع کا منصوبہ دراصل مزید machines خریدنے کا منصوبہ ہے۔ اس وقت تک یہ trade-off ایک node پر k3s چلانے جیسا ہی ہے، جہاں ایک node کی reliability کے لیے cluster complexity برداشت کی جاتی ہے۔

Kafka میں backlog کو consumer lag کہتے ہیں۔

bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group

LAG column پڑھیں۔ ہر partition کے لیے یہ LOG-END-OFFSET minus CURRENT-OFFSET کے برابر ہوتا ہے۔ اگر ایک partition کا lag بڑھ رہا ہو اور باقی partitions کا lag تقریباً مستقل رہے تو اس سے uneven key کا امکان ظاہر ہوتا ہے، کیونکہ ایک ہی key والے تمام messages اسی partition میں جاتے ہیں اور انہیں صرف ایک consumer handle کرتا ہے۔

ری اسٹارٹ کے وقت کیا ہوتا ہے

Core NATS دورانِ ترسیل موجود تمام data کھو دیتا ہے اور فوراً دوبارہ دستیاب ہو جاتا ہے، کیونکہ بحال کرنے کے لیے کچھ موجود نہیں ہوتا۔ JetStream streams اور consumer positions کو store directory سے دوبارہ load کرتا ہے، اس لیے consumers اسی offset سے دوبارہ شروع ہوتے ہیں جہاں وہ رک گئے تھے۔ RabbitMQ quorum queues کو disk سے بحال کرتا ہے، جبکہ classic transient queues اور persistent delivery mode کے بغیر publish کیے گئے تمام messages ختم ہو جاتے ہیں۔ Kafka startup کے وقت اپنے log segments دوبارہ پڑھتا ہے۔ غیر صاف shutdown کے بعد broker کے connection قبول کرنے سے پہلے یہ recovery scan چھوٹی disk پر کئی minutes لے سکتا ہے۔

دو settings ایک بار مقرر کرنا مفید ہے۔ Container کو restart policy (restart: unless-stopped) دیں یا systemd unit فعال کریں، تاکہ kernel upgrade کے بعد reboot ہونے پر broker خود واپس شروع ہو جائے۔ پھر startup order کا انتظام کریں۔ اگر broker آپ کی application کے ready ہونے کے 20 seconds بعد ready ہوتا ہے تو وہ پہلی connections مسترد کر دے گا، اور کچھ client libraries retry کرنے کے بجائے exit ہو جاتی ہیں۔ Compose healthchecks کے ذریعے application کو broker کے readiness تک روکیں۔

اپنے VPS پر لاگت: بیان کردہ اعداد کے بجائے پیمائش

شائع شدہ throughput کے اعداد و شمار ایسے hardware پر ناپے گئے ہوتے ہیں جو آپ کے پاس نہیں ہوتا، عموماً local NVMe والے multi-core server پر۔ انہیں بالائی حد سمجھیں اور اپنے server کی پیمائش کریں۔

docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_data

ان commands کو پہلے broker کے idle ہونے پر چلائیں، پھر اپنی حقیقی traffic کے دوران دوبارہ چلائیں۔ دونوں نتائج کے درمیان فرق یہ طے کرتا ہے کہ broker آپ کی application کے ساتھ چل سکتا ہے یا نہیں۔ throughput کی ایک تقریبی کم از کم حد معلوم کرنے کے لیے کسی کے blog post کے بجائے ہر project کا اپنا load generator استعمال کریں: NATS کے لیے nats bench pub test --msgs 100000 --clients 2، Kafka کے لیے bin/kafka-producer-perf-test.sh، اور RabbitMQ کے لیے PerfTest۔ اسی VPS پر generator چلانے سے broker اور generator دونوں کی مشترکہ performance ناپی جاتی ہے۔ یہ طریقہ درست ہے، بشرطیکہ report میں واضح کریں کہ عدد اسی طرح حاصل کیا گیا ہے۔

ان سب پر ایک ہی حد لاگو ہوتی ہے۔ یہاں ہر durable option fsync مکمل ہونے کا انتظار کرتا ہے۔ اس لیے network-attached storage والے VPS پر disk ہی حد مقرر کرتی ہے، اور broker تبدیل کرنے سے یہ حد نہیں بدلے گی۔

تین workloads اور ہر ایک کی مطلوبہ message queue

  1. ایک web application کے background jobs، مثلاً email بھیجنا، images کا سائز تبدیل کرنا، یا webhooks deliver کرنا۔ Postgres اور SKIP LOCKED سے شروع کریں۔ RabbitMQ کی quorum queues پر اس وقت منتقل ہوں جب آپ کو ہر message کے لیے acknowledgements، delivery limit، اور ایسی dead letter queue درکار ہو جس کا معائنہ اس منطق کو خود لکھے بغیر کیا جا سکے، یا جب job table database کی سب سے زیادہ مصروف table بن جائے۔
  2. ایسے events جن پر کئی internal services عمل کرتی ہوں، اور جن میں lost message کی جگہ جلد ہی نیا message لے لیتا ہو۔ Core NATS استعمال کریں، جہاں subjects routing scheme کے طور پر اور queue groups کام کی تقسیم کے لیے استعمال ہوں۔ صرف ان subjects کے محدود مجموعے کے لیے JetStream stream شامل کریں جنہیں restart کے بعد بھی محفوظ رہنا ضروری ہے، اور باقی messages memory میں رہنے دیں۔
  3. ایسا event log جسے consumers ابتدا سے پڑھیں، مثلاً audit trail کے لیے، read model دوبارہ بنانے کے لیے، یا بعد میں analytics کو feed کرنے کے لیے۔ JetStream کو file storage اور واضح byte cap کے ساتھ استعمال کریں۔ Kafka صرف اس وقت منتخب کریں جب downstream tool کو Kafka protocol درکار ہو، اور اس compatibility کی قیمت کے طور پر JVM heap قبول کریں۔

ایک server پر غلط انتخاب کی قیمت throughput نہیں ہے۔ اصل قیمت صبح 3 بجے ہونے والی recovery ہے، جب آپ کو معلوم کرنا ہو کہ messages اب بھی موجود ہیں یا نہیں۔ اسی بنیاد پر انتخاب کریں۔

FAQ

کیا میں 2 GB VPS پر Kafka چلا سکتا ہوں؟

یہ شروع ہو جائے گا، لیکن وسائل بہت محدود ہوں گے۔ اگر آپ نے اسے override نہ کیا ہو تو bin/kafka-server-start.sh، KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" مقرر کرتا ہے۔ اس لیے JVM کوئی بھی message محفوظ کرنے سے پہلے 1 GB میموری حاصل کر لیتا ہے، جبکہ Kafka کو page cache کے لیے اس کے علاوہ free memory درکار ہوتی ہے۔ اسی machine پر اپنی application اور database شامل کرنے سے system swap استعمال کرنے لگے گا۔ آپ کو replication factor 1 بھی ملے گا، یعنی acks=all ایک disk پر ایک fsync ہے۔ اس طرح آپ Kafka کے operational اخراجات تو ادا کرتے ہیں، لیکن اس کا durability model حاصل نہیں ہوتا۔ NATS JetStream اسی hardware پر بہت کم memory کے ساتھ durable replay فراہم کرتا ہے۔

اگر میں پہلے ہی Postgres چلا رہا ہوں تو کیا مجھے message queue کی ضرورت ہے؟

اکثر نہیں۔ transaction کے اندر SELECT ... FOR UPDATE SKIP LOCKED کے ساتھ job table کو پڑھنے سے at-least-once delivery، محفوظ concurrent workers، retries اور dead letter table حاصل ہو جاتے ہیں۔ اس کے لیے کسی اضافی service کی monitoring درکار نہیں ہوتی، اور backups بھی وہی رہتے ہیں جو آپ پہلے ہی لیتے ہیں۔ منتقلی کی وجوہات مخصوص ہوتی ہیں: queue table آپ کے سب سے زیادہ write load کا ذریعہ بن جائے اور autovacuum پیچھے رہ جائے؛ طویل دورانیے کے jobs transactions کھلی رکھیں اور پورے database میں vacuum کو روکیں؛ یا دوسری service کو انہی events کو آزادانہ طور پر consume کرنے کی ضرورت ہو۔

Background jobs کے لیے NATS JetStream یا RabbitMQ میں سے کس کو استعمال کرنا چاہیے؟

اگر آپ built-in behaviour کے طور پر per-message acknowledgement، delivery limit اور dead letter routing چاہتے ہیں تو RabbitMQ استعمال کریں۔ Quorum queues ہمیشہ durable ہوتی ہیں، delivery limit RabbitMQ 4.0 سے default طور پر 20 ہے، اور ایک policy ختم ہو جانے والے messages کو dead letter exchange میں بھیجتی ہے، جسے آپ drain اور inspect کر سکتے ہیں۔ اگر انہی events کو بعد میں دوسرے consumers کے ذریعے replay بھی کرنا ہو تو JetStream استعمال کریں، کیونکہ stream acknowledgement کے بعد messages محفوظ رکھتا ہے، جبکہ queue ایسا نہیں کرتی۔ JetStream میں آپ --max-deliver مقرر کرتے ہیں اور $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> advisory سے خود dead letter path بناتے ہیں۔

میں کیسے معلوم کروں کہ میرے consumers کتنے پیچھے ہیں؟

ہر broker کے لیے ایک command موجود ہے۔ RabbitMQ میں rabbitmqctl list_queues name messages messages_ready messages_unacknowledged consumer کے منتظر کام کو اس کام سے الگ کرتا ہے جو deliver ہو چکا ہے لیکن اس کا ack نہیں ہوا۔ JetStream میں nats consumer report <stream> ہر consumer کے لیے unprocessed messages اور outstanding acknowledgements دکھاتا ہے۔ Kafka میں kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> ہر partition کے لیے ایک LAG column print کرتا ہے۔ Core NATS میں پڑھنے کے لیے کوئی backlog نہیں ہوتا، کیونکہ یہ کچھ محفوظ نہیں کرتا۔ اس کے بجائے http://localhost:8222/varz پر slow_consumers counter کو monitor کریں۔ یہ ان connections کی تعداد گنتا ہے جنہیں server نے پیچھے رہ جانے کی وجہ سے بند کیا؛ اس کا مطلب message loss ہے۔

#nats#rabbitmq#kafka#message-queue#architecture