data

Coadă vs stream: când alegi RabbitMQ și când Kafka

Coada trimite fiecare mesaj unui singur consumer și îl șterge după ack. Stream-ul e un log distribuit persistent, redat oricând. Ghid de decizie practic.

Cuprins

O coadă de mesaje trimite fiecare mesaj unui singur consumer, iar după confirmare (acknowledgement) îl șterge. Un stream este un log distribuit persistent în care mesajele rămân și pot fi recitite de oricâți consumatori, de la orice offset. Alegerea greșită nu strică funcționalitatea, dar produce costuri operaționale complet diferite pe termen lung.

Confuzia cea mai frecventă: RabbitMQ și Kafka nu sunt două implementări ale aceluiași concept. RabbitMQ e un message broker; Kafka e un distributed log. Că amândouă sunt invocate în același context înseamnă că întrebarea reală e despre ce anume vrei să modelezi.

Ce înseamnă coadă (RabbitMQ, SQS) mai exact?

O coadă funcționează cu semantică push-pop: producătorul depune un mesaj, un singur consumer îl preia, procesează și trimite un ack. Brokerul șterge mesajul după ack. Dacă consumerul cade înainte să confirme, mesajul revine în coadă și e redistribuit.

  • Livrare la un singur consumer. Un mesaj ajunge la un singur consumer per queue, indiferent câți consumatori rulează în paralel. Mai mulți consumatori înseamnă paralelizare, nu fanout.
  • Acknowledgement explicit. Fără ack, mesajul rămâne în zbor și poate fi redelivrat după un timeout configurat.
  • Ștergere după ack. Brokerul nu păstrează istoricul. Nu există replay.
  • Dead-letter queue. Mesajele care eșuează repetat pot fi redirectate automat într-o coadă separată pentru inspecție.

RabbitMQ și Amazon SQS implementează această semantică. Potrivite pentru joburi de background, trimiteri de email, procesare de plăți, orice flux unde fiecare sarcină se execută o singură dată de un singur actor.

Ce înseamnă stream (Kafka, Kinesis, Redpanda)?

Un stream este un log append-only distribuit. Producătorii adaugă mesaje la capătul log-ului; consumatorii citesc de la un offset specificat și avansează independent. Mesajele nu se șterg după citire, ci după expirarea unui TTL configurat.

  • Replay. Orice consumer poate citi de la offsetul zero și reconstrui starea completă. Esențial pentru audit, migrări de date, debug în producție.
  • Consumer groups independente. Doi consumatori din groupuri diferite citesc același mesaj independent, fiecare cu propriul offset. Fără copii fizice.
  • Partiționare și ordering. Ordinea garantată e doar în interiorul unei partiții. Mesajele cu aceeași cheie de partiționare ajung mereu la aceeași partiție.
  • Retenție configurabilă. Consumatorii care au întârziat nu pierd mesaje, ci se readresează istoricului.

Kafka, Amazon Kinesis și Redpanda implementează această semantică. Potrivite pentru event sourcing, stream processing, audit logs, sincronizare între servicii cu ritmuri diferite de procesare.

Care sunt diferențele semantice (consumer group, ack, replay, ordering)?

Terminologia pare similară, dar conceptele nu sunt echivalente:

  • Ack în coadă vs offset în stream. În RabbitMQ, fiecare mesaj cere un ack explicit; fără el, mesajul e considerat în procesare. În Kafka, consumerul avansează un offset. O cădere înainte de commit înseamnă repreluare de la ultimul offset confirmat, nu de la mesajul individual.
  • Consumer group în RabbitMQ vs Kafka. În RabbitMQ, mai mulți consumatori pe aceeași queue formează un pool de procesare competitivă. În Kafka, un consumer group primește câte o partiție per instanță; două groupuri diferite văd independent toate datele. Nu sunt același lucru.
  • Ordering. RabbitMQ garantează ordinea cu un singur consumer pe queue. Kafka garantează ordinea per partiție. O singură partiție dă ordine globală, dar elimini paralelismul.
  • Replay și audit. Cozile nu au replay; odată cu ack-ul, mesajul dispare din broker. Stream-urile au replay nativ. Dacă vrei să reprelucrezi evenimentele din ultima săptămână, ai nevoie de Kafka sau de un storage secundar cu cozile.

Cum decidem ce alegem pe un proiect nou?

Câteva întrebări care clarifică alegerea rapid:

  • Fiecare mesaj trebuie procesat de un singur serviciu? Dacă da, coadă. Dacă mai mulți consumatori independenți trebuie să vadă același eveniment, stream.
  • Ai nevoie de replay sau audit? Dacă da, stream. Replayul e arhitectural imposibil cu o coadă standard.
  • Ordinea globală e obligatorie? Nici coada, nici stream-ul nu garantează ordine globală cu paralelism real. O singură partiție Kafka dă ordine, dar sacrifici throughput.
  • Cine va opera sistemul și cât de repede trebuie să fie în producție? RabbitMQ pornește fără configurare. Kafka are overhead operațional semnificativ mai mare, mai ales în primele luni. Echipele mici supraestimează constant cât timp vor investi în tuning.
  • Ai deja n8n în stack? n8n integrează nativ cu RabbitMQ prin AMQP și simplifică rutarea mesajelor fără cod suplimentar. Dacă orchestrezi fluxuri prin n8n, coada e alegerea cu cel mai mic friction.

Heuristic util: dacă nu știi cu certitudine că ai nevoie de replay sau multi-consumer independent, pornești cu o coadă și migrezi când cerința devine concretă.

Care sunt capcanele operaționale (back-pressure, partition skew, monitoring)?

  • Back-pressure negestionat. Dacă producătorii scriu mai rapid decât consumatorii procesează, coada crește și consumă memorie pe broker. RabbitMQ poate cădea când RAM-ul se umple fără limite configurate. Monitorizează queue depth ca primă metrică, nu throughput-ul brut.
  • Partition skew în Kafka. O cheie de partiționare cu cardinalitate redusă (3 valori pe 12 partiții) supraîncarcă câteva partiții în timp ce restul stau. Verifică distribuția înainte de lansare.
  • Lipsa DLQ sau DLT. Un mesaj care nu poate fi procesat blochează coada sau partiția fără un mecanism de redirecționare. Configurează dead-letter queue și dead-letter topic de la primul deploy.
  • Offset commit prematur în Kafka. Commit înainte de procesare înseamnă pierdere de mesaje la restart. Commit-ul se face după procesare, nu înainte.
  • Idempotența ignorată. Ambele sisteme pot relivreze mesaje la reconectare sau la repreluare de offset. Proiectează consumatorul să tolereze același input de mai multe ori fără efecte duplicate.
  • Observabilitate lipsă. Fără metrici pe broker nu știi dacă coada crește sau dacă consumatorii sunt în urmă. RabbitMQ expune metrici Prometheus nativ; pentru Kafka există Kafka Exporter. Configurează de la primul deploy; fără consumer lag pe un grafic, operezi cu ochii închiși.

Dacă folosești Postgres ca storage pentru starea procesată, un câmp de tip idempotency_key unic pe tabelul de destinație este apărarea de bază împotriva dublărilor. Schema se versionează cu un instrument de migrare precum Liquibase, astfel că modificările sunt reproductibile. La crawlerra, când orchestrăm automatizări și fluxuri de notificări folosim n8n, care vine cu propriul model de coadă internă; un broker dedicat (RabbitMQ sau Kafka) îl introducem doar când avem dovezi de back-pressure persistent sau de cerințe clare de replay pe care n8n nu le poate acoperi. Despre gestionarea traficului pe cozile de scraping, vezi intrarea despre rate limiting.

Întrebări frecvente

RabbitMQ vs Kafka, care e mai simplu de pornit?

RabbitMQ pornește în Docker în mai puțin de un minut și nu cere configurare suplimentară pentru un proiect nou. Kafka are nevoie (sau a avut nevoie, înainte de KRaft) de ZooKeeper, are mai mulți parametri de tuning și produce erori mai cryptice în primele ore. Dacă nu ai un caz clar de replay sau de multi-consumer fanout independent, pornești cu RabbitMQ și migrezi mai târziu dacă ai motive concrete.

Pot consuma același mesaj de mai multe ori cu RabbitMQ?

Nu, în configurarea standard, fiecare mesaj merge la un singur consumer și e șters după ack. Dacă ai nevoie ca mai mulți consumatori să vadă același mesaj independent, folosești un exchange de tip fanout sau topic, dar fiecare queue abonată primește câte o copie. Cu Kafka, toți consumatorii din consumer groups diferite văd fiecare mesaj fără copii, log-ul se reține.

Ce se întâmplă când un consumer e oprit în Kafka?

Mesajele se acumulează în log și sunt preluate de unde a rămas consumerul când repornește. Kafka ține minte offsetul pentru fiecare consumer group. Cu RabbitMQ, mesajele neacknowledge-uite se re-enqueue automat dacă nu configurezi un dead-letter exchange. Comportamentul implicit e diferit și contează să știi de la început ce vrei.

Kafka merge pe un VPS mic?

Merge, dar nu e optimizat pentru asta. Kafka folosește un flush agresiv pe disc și recomandă minimum 6 GB RAM pentru un broker de producție cu trafic moderat. Pe un VPS de 2 GB, vei trăi cu presiune pe memorie și restarturi neașteptate. RabbitMQ rulează confortabil pe 512 MB RAM pentru sarcini mici. La buget limitat, coada câștigă.

n8n folosește RabbitMQ sau Kafka în mod nativ?

n8n se integrează cu ambele, dar cel mai natural folosit este RabbitMQ prin nodul AMQP. Kafka are un nod dedicat în n8n, dar configurarea e mai verbosă. Pentru automatizări și pipeline-uri de notificări la scară medie, RabbitMQ plus n8n este combinația cu cel mai mic overhead operațional.