Redis Streams, AMQP, and the Doctrine transport: throughput, persistence, and operational cost side by side
Symfony Messenger hides the actual queue behind a single DSN configuration, but underneath that abstraction sit fundamentally different backends with very different characteristics. Choosing Doctrine, Redis, or AMQP for a new project implicitly decides throughput limits, persistence guarantees in a crash, and the operational burden the team will carry going forward. This article compares the three common transport backends along exactly these three dimensions and shows which backend genuinely fits which use case.
Table of Contents
- 1. Why the choice of transport backend can make or break a system
- 2. The Doctrine transport: the database as a queue
- 3. The Redis Streams transport: a fast in-memory queue with consumer groups
- 4. The AMQP transport (RabbitMQ): a robust message broker protocol
- 5. Throughput compared: polling versus push based delivery
- 6. Persistence guarantees: what happens during a crash?
- 7. Operational complexity: running, monitoring, and scaling
- 8. Which backend fits which use case
- 9. Migration paths and hybrid strategies in practice
- 10. Summary
- 11. FAQ
1. Why the choice of transport backend can make or break a system
Messenger hides the concrete transport behind a simple DSN like doctrine://default, redis://localhost, or amqp://guest:guest@localhost, and from the handler code's point of view, switching backends changes literally nothing. That very convenience tempts people to dismiss the backend choice as a mere configuration detail, when in reality it determines how many messages per second the system can realistically process, what happens to already enqueued messages during a crash, and how much extra infrastructure a team has to run indefinitely.
The three backends most commonly used in Symfony projects are the Doctrine transport, which uses the database as a queue, the Redis transport built on Redis Streams, and the AMQP transport, which typically talks to RabbitMQ. All three fulfill the same basic job but differ so strongly in their characteristics that a deliberate decision based on the actual use case is necessary, rather than blindly trusting a default configuration copied from a tutorial.
2. The Doctrine transport: the database as a queue
The Doctrine transport stores every message as a row in a table called messenger_messages, which gets created automatically when the auto_setup option is enabled. The worker polls this table periodically and locks a found message with SELECT ... FOR UPDATE, adding SKIP LOCKED on MySQL 8 and PostgreSQL so multiple parallel workers do not block each other and do not process the same message twice.
The big advantage is simplicity: no additional infrastructure is required, messages sit in the same database as the application data and automatically benefit from its backup strategy and ACID guarantees. The downside is the polling overhead: every polling cycle generates load on the database, and at high message frequency the database itself quickly becomes the bottleneck, since it has to serve the actual application queries at the same time.
3. The Redis Streams transport: a fast in-memory queue with consumer groups
The Redis transport is built on Redis Streams, a data structure available since Redis 5 that internally uses XADD to enqueue and XREADGROUP for blocking retrieval through named consumer groups. These consumer groups make sure multiple parallel workers automatically split up the messages of a stream, without Messenger itself needing any distribution logic, and unacknowledged messages can be found again through the so called pending entries list.
The configuration below shows a typical Redis transport with a named stream, a consumer group, and auto setup enabled, which automatically creates the stream and the group on first start if they do not exist yet.
framework:
messenger:
transports:
async_redis:
dsn: '%env(REDIS_MESSENGER_DSN)%'
options:
stream: 'orders_stream'
group: 'order_processors'
consumer: 'worker-1'
auto_setup: true
delete_after_ack: true
delete_after_reject: true
4. The AMQP transport (RabbitMQ): a robust message broker protocol
The AMQP transport speaks the Advanced Message Queuing Protocol, in practice almost always against a RabbitMQ server, and maps exchanges, queues, and routing keys directly into the DSN and transport options. RabbitMQ natively supports persistent messages through the 'persistent' delivery mode, durable queues that survive a broker restart, and built in dead lettering, where permanently rejected messages get automatically routed to a separate queue.
The advantage is a mature, decades old protocol with excellent tooling support, such as the RabbitMQ management interface for live queue inspection, and client libraries for practically every programming language, which makes RabbitMQ particularly attractive in polyglot system landscapes. The downside is operational cost: RabbitMQ runs as its own service on top of the Erlang runtime, and a highly available setup with quorum queues or mirroring demands considerably more operational experience than a pure database or Redis solution.
5. Throughput compared: polling versus push based delivery
The Doctrine transport polls periodically with a short pause between empty queries, and every cycle means a full database transaction including a lock. In practice that is enough for a few hundred to a few thousand messages per second, depending on the database hardware, before latency starts to climb noticeably.
Redis Streams and AMQP, in contrast, work in a blocking or push based fashion: XREADGROUP with the BLOCK option waits efficiently for new entries without active polling, and RabbitMQ actively delivers messages to waiting consumers. Both backends typically reach a substantially higher throughput, often in the tens of thousands of messages per second on comparable hardware, which clearly favors them for scenarios with high message frequency.
6. Persistence guarantees: what happens during a crash?
With the Doctrine transport, messages are as durable as the underlying database itself: a commit lands in the transaction log, gets covered by regular backups, and an application server crash can practically never lose an already committed message, which is the strongest guarantee among the three backends.
With AMQP, durable queues combined with persistent messages reach a similarly strong guarantee, but require correct, explicit configuration, since non-persistent messages get lost on a broker restart. Redis, in its default setup with periodic RDB snapshots, offers a weaker guarantee, since data written between two snapshots can be lost during a crash, while an AOF configuration with appendfsync always reduces that risk to nearly zero at a noticeable write performance cost.
7. Operational complexity: running, monitoring, and scaling
The Doctrine transport requires not a single additional service, only an extra table and indexes in an already existing database, and can be monitored with the same tools already used for the database, keeping the onboarding cost minimal.
Redis is comparatively lightweight to run as an extra service, though a cluster setup for genuine high availability with automatic failover (Redis Sentinel or cluster mode) adds meaningful complexity. AMQP, or RabbitMQ, offers the most powerful feature set of routing rules, priorities, and dead lettering, but demands the highest operational effort in return, especially in a cluster with quorum queues for fault tolerance.
8. Which backend fits which use case
For small to medium projects with few messages per second, where no team is available to run additional infrastructure, the Doctrine transport is usually the most pragmatic choice: it reuses infrastructure that already exists, and its throughput limits are rarely reached in practice.
Once a project grows toward high message frequency or genuine near real time processing, a lot speaks for Redis Streams, especially when Redis is already in use as a cache and no new technology needs to be introduced. Complex routing requirements with multiple consumer types, guaranteed message order per routing key, or strict company wide delivery guarantees, on the other hand, favor AMQP and RabbitMQ despite the higher operational effort.
9. Migration paths and hybrid strategies in practice
Since Messenger can route every message type individually to a transport through the routing configuration, a project does not have to commit to a single backend: critical payment messages can run over AMQP with guaranteed delivery, while non-critical analytics events get processed through the simpler Doctrine or Redis transport.
Switching from Doctrine to Redis or AMQP as traffic grows is straightforward thanks to this abstraction, since the handler code itself remains completely unchanged and only the DSN and transport configuration need to be adjusted. The pragmatic recommendation is therefore to start a project with the simplest fitting backend and only move to a more capable but operationally heavier backend once an actual, measured need arises.
| Criterion | Doctrine transport | Redis Streams | AMQP (RabbitMQ) |
|---|---|---|---|
| Throughput | Low to medium | High | High |
| Persistence guarantee | Very high (DB ACID) | Medium, depends on AOF/RDB | High with durable + persistent |
| Operational effort | Very low | Low to medium | High (dedicated cluster) |
| Setup complexity | Very simple | Simple | Demanding |
| Typical use case | Small to medium projects | High throughput, real time | Complex routing, strict guarantees |
Mironsoft
Symfony architecture, clean domain logic, and legacy modernization
Symfony applications that stay maintainable two years down the line?
We review existing Symfony projects for bloated controllers, missing service abstractions, and untested core logic, then build an architecture that absorbs new features without getting more fragile with every release.
Architecture Review
Checking bundle structure, dependency injection, and service abstractions for maintainability.
Legacy Modernization
Incrementally migrating outdated Symfony versions without a full rewrite.
Testing and Quality Assurance
Setting up PHPUnit, PHPStan, and CI pipelines for lasting code quality.
10. Summary
Messenger Transport Backends: The Key Points at a Glance
Doctrine
Reuses the existing database, minimal operational effort, but polling based and throughput limited.
Redis Streams
High throughput and low latency through consumer groups, persistence depends on Redis configuration.
AMQP/RabbitMQ
The most powerful routing and strongest delivery guarantees, at the highest operational cost.
Decision rule
Start with the simplest fitting backend and only switch once a real, measured need arises.