Kolejki w e-commerce - RabbitMQ, Redis, SQS, Kafka
Każdy projekt sklepu B2B w skali ma moment, w którym kolejki przestają być „dobrym pomysłem na później" i stają się ratunkiem. Najczęściej moment ten przychodzi w nocy, gdy ERP klienta restartuje się na 4 godziny i wszystkie nowe zamówienia walą się 500-tkami. Z kolejkami - nikt nawet nie zauważy. Bez nich - telefon o 3 nad ranem.
Spis treści (11)
W skrócie
- 1. Kolejki = każda operacja asynchroniczna w architekturze sklepu
- 2. Co przez kolejkę: zamówienia → ERP, synchronizacje, emaile, reindeksowanie, marketplace sync
- 3. Co synchronicznie: koszyk, checkout, walidacje przy zamówieniu
- 4. Technologie polskiego B2B: RabbitMQ (najczęściej), Redis Streams (mniejsza skala), SQS (na AWS)
- 5. Must-have wzorce: idempotencja, retry z exponential backoff, dead letter queue, monitoring
Co konkretnie powinno przejść przez kolejkę
Lista operacji typowego sklepu B2B i decyzja kolejka / synchronicznie:
| Operacja | Kolejka? | Powód |
|---|---|---|
| Zamówienie → ERP | TAK | ERP może być wolne / niedostępne |
| Email "Dziękujemy za zamówienie" | TAK | SMTP może być wolne |
| SMS o statusie | TAK | API SMS może mieć rate limit |
| Synchronizacja stanów ERP → sklep | TAK | Wolumen + retry |
| Synchronizacja cenników ERP → sklep | TAK | Wolumen + retry |
| Webhook do BaseLinkera o nowym zamówieniu | TAK | External system, niestabilność |
| Reindeksowanie Elasticsearch | TAK | Powolne, ale niekrytyczne |
| Walidacja koszyka | NIE | Klient czeka |
| Walidacja limitu kredytowego przy checkout | NIE | Decyzja synchroniczna |
| Płatność online (callback z bramki) | TAK | Bramki to external |
| Generowanie raportu PDF | TAK | Może trwać sekundy |
| Push notification | TAK | External provider |
Reguła: jeśli klient nie czeka na rezultat - kolejka. Jeśli czeka (koszyk, checkout) - synchronicznie.
RabbitMQ - domyślny wybór dla średniego B2B
RabbitMQ to message broker oparty na AMQP, najpopularniejszy w polskim PHP/Symfony świecie.
Mocne strony:
- Bardzo dojrzały (od 2007)
- Dobre wsparcie w PHP/Symfony (Symfony Messenger ma RabbitMQ transport)
- Exchange + Queue + Binding = elastyczne routing
- Dead letter queue (DLQ) wbudowane
- Management UI (przeglądasz kolejki w przeglądarce)
- Klastrowanie i replikacja
Słabe strony:
- Wymaga osobnego procesu (RabbitMQ server) i konfiguracji
- Persistence przez Mnesia (specyficzna baza Erlanga)
- Performance dobry, ale nie świetny dla bardzo dużych wolumenów (powyżej setek tysięcy / sekundę)
Typowa konfiguracja w sklepie B2B:
Exchange: orders (direct)
Queue: orders.to_erp (binding: routing_key=erp)
Queue: orders.to_baselinker (binding: routing_key=baselinker)
Queue: orders.to_email (binding: routing_key=email)
Exchange: products (fanout)
Queue: products.reindex_search
Queue: products.invalidate_cache
Queue: products.sync_marketplace
Dead Letter Exchange (DLX): dlq
Queue: dlq.all (catch-all dla failed messages)
Hosting RabbitMQ:
- Self-hosted: 1-2 maszyny (HA cluster), ~500-1500 zł / mies.
- Managed: CloudAMQP (od $20/mies. do $1000+ dla większych)
- AWS: Amazon MQ for RabbitMQ
Redis Streams - lekka alternatywa
Redis Streams (dodane w Redis 5.0, 2018) to wbudowany w Redis mechanizm kolejek. Lżejszy niż RabbitMQ.
Mocne strony:
- Jeden serwis (Redis), nie dwa
- Bardzo szybki (memory-based)
- Consumer groups dla skalowania
- Persistence przez Redis (RDB/AOF)
- Dobry dla mniejszej skali
Słabe strony:
- Mniej funkcji niż RabbitMQ (brak exchange-routing, słabsze DLQ patterns)
- Persistence słabsza (Redis może gubić dane przy awarii)
- Mniej dojrzały ekosystem
Kiedy Redis Streams:
- Mała / średnia skala (do ~10k messages/min)
- Już używasz Redis dla cache i sesji
- Chcesz minimum dodatkowej infrastruktury
Kiedy NIE Redis Streams:
- Duża skala (powyżej 50k+ messages/min)
- Skomplikowane routing (fanout, topic exchanges)
- Krytyczne wymagania persistence (nie chcesz tracić nawet jednego message)
AWS SQS / Google Pub/Sub - dla cloud-hosted
Jeśli sklep na AWS - naturalny wybór to SQS. Plusy:
- Managed (nie utrzymujesz)
- Skalowanie automatyczne
- Wbudowany DLQ
- Tania (kilka USD / mies. dla średniego sklepu)
Minus:
- Lock-in w AWS
- Mniej elastyczne routing niż RabbitMQ
Analogicznie Google Cloud Pub/Sub i Azure Service Bus.
Kafka - gdy skala wymusza
Apache Kafka to message broker dla bardzo dużej skali. Eventy persistowane jako log, replay możliwy.
Kiedy Kafka:
- Skala 100k+ events / sec
- Multi-konsument (kilku konsumentów tego samego eventu)
- Event sourcing
- Stream processing
W polskim B2B średniej skali: rzadko. Kafka to znaczna inwestycja w setup i operacje.
Symfony Messenger - abstrakcja, którą warto
Symfony Messenger to component frameworka Symfony, który abstraktuje konkretną technologię kolejek.
// Wysyłanie message - niezależnie od transportu
$bus->dispatch(new OrderToERPMessage($orderId));
// Konfiguracja transportu w yaml - można zmienić bez ruszania kodu
framework:
messenger:
transports:
erp_sync: 'amqp://guest:guest@rabbitmq:5672/%2f/messages'
# lub: 'redis://redis:6379/messages'
# lub: 'sqs://...'
Plus: zaczynasz z Redis (proste), przechodzisz na RabbitMQ (skala), bez zmian w kodzie aplikacji.
Dla Magento / Shopware: używaj wbudowanego mechanizmu kolejek (Magento Message Queue Framework, Shopware Symfony Messenger).
Idempotencja - must-have
Idempotencja = wykonanie tej samej operacji wielokrotnie ma ten sam skutek co wykonanie jednokrotne.
Dlaczego ważne: retry. Jeśli operacja padnie, kolejka spróbuje ponownie. Jeśli operacja nie jest idempotentna - przy retry stworzysz duplikat.
Przykład złej praktyki:
// ZŁY KOD: każde wywołanie tworzy nowy dokument w ERP
$erp->createOrder($order);
Przykład dobrej praktyki:
// DOBRY KOD: external_reference jako idempotency key
$externalRef = 'WEB-' . $order->getId();
$existing = $erp->findOrderByReference($externalRef);
if ($existing) {
return $existing; // już istnieje, nic nie rób
}
$erp->createOrder($order, externalReference: $externalRef);
Idempotency keys w praktyce:
- Każde zdarzenie ma unikalny ID (UUID lub combo
sklep_id + entity_id) - Consumer sprawdza czy już przetworzył ten ID
- Jeśli tak - skip, oznaczy jako sukces
- Jeśli nie - przetwórz, zapisz
processed_event_idw tabeli idempotency
Retry i Dead Letter Queue
Retry z exponential backoff:
Message padnie? Nie próbuj natychmiast. Próbuj po: 1s, 2s, 4s, 8s, 16s, 32s, 60s. Razem 8 prób w okolicach 2 minut.
Powód: jeśli ERP padł - natychmiastowy retry nic nie da. Exponential backoff daje ERP-owi czas na powrót.
W Symfony Messenger:
framework:
messenger:
transports:
erp_sync:
dsn: 'amqp://...'
retry_strategy:
max_retries: 8
delay: 1000 # 1s
multiplier: 2 # 2x każdy retry
max_delay: 60000 # max 60s
Dead Letter Queue (DLQ):
Po wyczerpaniu retry - message ląduje w DLQ. To kolejka „wszystko poszło źle, zobacz".
DLQ to alarm. Każda wiadomość w DLQ to incydent do zbadania. Bez monitorowania DLQ - wiadomości giną.
Monitoring i alerty
Kolejka bez monitoringu = bomba zegarowa.
Co monitorować:
- Liczba wiadomości w kolejce - rośnie? Consumer nie nadąża, problem.
- Liczba wiadomości w DLQ - > 0 = alarm.
- Czas oczekiwania message (age) - wiadomość czeka 30 minut zamiast 30 sekund = problem.
- Consumer health - czy procesy konsumentów działają (Supervisor, systemd, k8s pods).
- Throughput - ile messages / sec przetwarzane.
Narzędzia:
- RabbitMQ Management Plugin (UI)
- Prometheus + Grafana
- Datadog APM
- New Relic
Alerty:
- DLQ > 0 → ticket / pager
- Queue depth > 10000 → warning
- Consumer down > 5 min → pager
Workery - jak je uruchamiać
Workery to procesy, które konsumują wiadomości z kolejek. Musi być coś, co je uruchamia i restartuje przy awariach.
Opcje:
1. Supervisor (Linux service manager).
Klasyczny wybór dla VPS / dedykowanych. Konfiguracja w INI, restart przy awarii, zarządzanie logami.
[program:order-sync-worker]
command=php bin/console messenger:consume erp_sync --time-limit=3600 --memory-limit=512M
process_name=%(program_name)s_%(process_num)02d
numprocs=4
autostart=true
autorestart=true
2. systemd.
Nowsze, w nowoczesnych Linuxach. Podobne do Supervisor.
3. Kubernetes Pods.
Każdy worker jako pod, replicas konfigurowalne, autoscaling. Dla projektów na K8s.
4. AWS ECS / Fargate.
Managed, dla AWS.
Liczba workerów:
Reguła: jeden worker per kolejka per CPU rdzeń. Czyli na 8-rdzeniowej maszynie z 4 kolejkami: 2 workery per kolejka.
Skalowanie: gdy queue depth rośnie - dodaj workerów. Gdy maleje - zmniejsz.
Najczęstsze błędy
1. Brak idempotencji. Retry → duplikaty.
2. Brak DLQ. Wiadomości giną cicho.
3. Synchronous calls w workerze. Worker czeka 30s na response z ERP, blokuje kolejkę.
4. Brak time limit / memory limit workerów. Workery puchną w pamięci, OOM po dniu.
5. Nieobsługiwane exceptions. Wyjątek nie złapany → worker crash → message zgubione.
6. Zbyt duże messages. Wpychanie całych obiektów do queue. Lepiej entity_id, dane pobiera worker.
FAQ
Czy mogę używać tylko cron'a zamiast kolejek? Cron działa dla batch'owych synchronizacji (co X minut). Nie działa dla zdarzeń real-time (zamówienie złożone teraz musi iść do ERP teraz). Dla typowego sklepu B2B kolejki są krytyczne.
Czy Magento ma własne kolejki? Tak, Magento Message Queue Framework (oparty na RabbitMQ lub MySQL). Dla custom integracji można i powinno się go używać.
Co z Shopware? Shopware używa Symfony Messenger. Standardowy stack PHP.
Kafka czy RabbitMQ? Dla większości polskich B2B średniej skali - RabbitMQ. Kafka dla bardzo dużych skala (50k+ events/sec) i event sourcing.
Czy potrzebuję managed RabbitMQ (CloudAMQP) czy self-hosted? Dla małych projektów self-hosted OK. Powyżej 100k messages/dziennie - managed jest spokojniej (HA, monitoring, backups).
Co dalej
- Architektura w szerszym kontekście: Architektura dużych sklepów
- Middleware używające kolejek: Middleware vs. bezpośrednio
- Integracja z ERP - gdzie kolejki kluczowe: Comarch XL
- Skalowanie bazy: Skalowanie DB
O autorze
Jakub Owsianka
Architekt rozwiązania w WiseB2B - silniku platform B2B. Zaczynał po stronie biznesu (własne sklepy), potem deweloper, dziś projektuje wdrożenia dla sklepów z katalogami w dziesiątkach tysięcy SKU. W ostatnich latach wdrożył AI-development w zespole i funkcjonalności oparte o AI bezpośrednio w silniku sklepu.
Masz pytanie do tego artykułu?
Dodatkowy kontekst, problem z własnym wdrożeniem, druga opinia - napisz wprost. Odpowiadam osobiście w 1-2 dni robocze.