Symfony Messenger Transport Backends Compared: Redis, AMQP and Doctrine
AI generated
SF
{ }
Symfony · Messenger · Infrastructure
Symfony Messenger Transport Backends Compared
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.

15 min read Messenger · Transport Comparison Redis · AMQP · Doctrine

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.

11. FAQ: Messenger Transport Backends: The Key Points at a Glance

1Can multiple transport backends be used at the same time in one Symfony project?
Yes, through the routing configuration every message type can be assigned to its own transport, so critical messages can go over AMQP while non-critical ones go over Doctrine.
2Is the Doctrine transport suitable for production environments?
Yes, for many small and medium projects it is entirely sufficient and offers very high reliability thanks to the database's ACID guarantees, as long as message frequency is not extremely high.
3How many messages per second can the Doctrine transport realistically handle?
It depends heavily on the database hardware, in practice it often falls in the range of a few hundred to low thousands of messages per second before polling overhead becomes noticeable.
4Does Redis guarantee losing messages during a crash?
Not guaranteed, but in the default setup with periodic RDB snapshots, messages written between two snapshots can be lost. An AOF configuration with appendfsync always reduces that risk to nearly zero.
5What is the advantage of AMQP over Redis for complex routing?
AMQP natively supports exchanges with different routing strategies, priority queues, and dead lettering, while Redis Streams primarily offers simple consumer group distribution without comparably granular routing.
6Does RabbitMQ need to run as a cluster?
For high availability yes, a single RabbitMQ node is a single point of failure. For smaller projects without strict availability requirements, a single node with regular backups is often enough.
7Is Redis worth it if Redis is already used as a cache?
Especially in that case, since no new infrastructure technology needs to be introduced and the team already has experience running Redis.
8Can you switch from Doctrine to Redis or AMQP later without much trouble?
Yes, since Messenger abstracts the transport behind the DSN, the handler code stays unchanged, only the DSN and transport options need to be adjusted.
9Which backend guarantees the strongest message ordering per message type?
AMQP with a single queue per routing key guarantees strict FIFO order. Redis Streams guarantees order within a stream, Doctrine guarantees it through insertion time ordering, but that can drift slightly with parallel workers.
10Is AMQP's operational effort too high for every project?
Not universally, for projects that already have RabbitMQ expertise or very high requirements around routing and delivery guarantees, the extra effort is justified. For most smaller projects, though, the effort outweighs the benefit.