Investigating & Retrying Failed Messages
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeApparently now that we've configured a failure_transport
, if handling a message still isn't working after 3 retries, instead of being sent to /dev/null
, they're sent to another transport - in our case called "failed". That transport is... really... the same as any other transport... and we could use the messenger:consume
command to try to process those messages again.
But, there's a better way. Run:
php bin/console messenger
Seeing Messages on the Failed Queue
Hey! Shiny new commands are hiding here! Three under messenger:failed
. Try out that messenger:failed:show
one:
php bin/console messenger:failed:show
Nice! There are our 4 failed messages... just sitting there wait for us to look at them. Let's pretend that we're not sure what went wrong with these messages and want to check them out. Start by passing the 115 id:
php bin/console messenger:failed:show 115
I love this: it shows us the error message, error class and a history of the message's misadventures through our system! It failed, was redelivered to the async transport at 05, at 06 and then at 07, it finally failed and was redelivered to the failed
transport.
If we add a -vv
on the command...
php bin/console messenger:failed:show 115 -vv
Now we can see a full stack trace of what happened on that exception.
This is a really powerful way to figure out what went wrong and what to do next: do we have a bug in our app that we need to fix before retrying this? Or maybe it was a temporary failure and we can try again now? Or maybe, for some reason, we just want to remove this message entirely.
If you did want to remove this without retrying, that's the messenger:failed:remove
command.
Retrying Failed Messages
But... let's retry this! Back in the handler, change this back to fail randomly.
// ... lines 1 - 13 | |
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface | |
{ | |
// ... lines 16 - 30 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
// ... lines 33 - 46 | |
if (rand(0, 10) < 7) { | |
throw new \Exception('I failed randomly!!!!'); | |
} | |
// ... lines 50 - 56 | |
} | |
} |
There are two ways to work with the retry command: you can retry a specific id like you see here or you can retry the messages one-by-one. Let's do that. Run:
php bin/console messenger:failed:retry
This is kind of similar to how messenger:consume
works, except that it asks you before trying each message and, instead of running this command all the time on production, you'll run it manually whenever you have some failed messages that you need to process.
Cool! We see the details and it asks if we want to retry this. Like with show, you can pass -vv
to see the full message details. Say "yes". It processes... and then continues to the next. Actually, let me try that again with -vv
so we can see what's going on:
php bin/console messenger:failed:retry -vv
When Failed Messages... Fail Again
This time we see all the details. Say "yes" again and... nice: "Received message", "Message handled" and onto the next message. We're on a roll! Notice that this message's id is 117 - that'll be important in a second. Hit yes to retry this message too.
Woh! This time it failed again! What does that mean? Well remember, the failure transport is really just a normal transport that we're using in a special way. And so, when a message fails here, Messenger... retries it! Yea it was sent back to the failure transport!
I'll hit Control+C and re-run the show command:
php bin/console messenger:failed:show
That id 119 was not there when we started. Nope, when message 117 was processed, it failed, was redelivered to the failure transport as id 119, and then was removed. And so, unless you change your configuration, messages will be retried 3 times on the failure transport before finally being completely discarded.
Oh, but if you look at the retried message closer:
php bin/console messenger:failed:show 119 -vv
There's a bit of a bug: the error and error class are missing. The data is still in the database... it's just not displayed correctly here. But you can see the message's history: including that it was sent to the failed
transport and then sent again to the failed
transport.
By the way, you can pass a --force
option to the retry
command if you want it to retry messages one-by-one without asking you each time whether or not it should do it. Also, not all the transport types - like AMQP or Redis - support all of the features we just saw if you use it as your failure transport. That may change in the future, but at this moment - Doctrine is the most robust transport to use for failures.
Anyways, as cool as failing is, let's go back and remove the code that's breaking our handler. Because... it's time to take a step deeper into how Messenger works: it's time to talk about middleware.
Actually, I wouldn't say "Doctrine is the most robust transport to use for failures".
1. you have one failed transport for every other transport. You can't have handle failed messages from other transports in different ways. E.g. Failed messages from transport A should be handled every 1 hour, failed messages from transport B should be handled every 5 hours.
2. you cannot move messages failed messages from transport to transport, e.g. after X retries on transport A message should be moved to less prioritized transport B not to block more prioritized transport A.
With rabbitmq you have this advantage that rabbit is acknowledged after max_retries that message was rejected and you can forward this message to the dead letter exchange (or the same exchange but different queue with another routing key) which is configured for another transport in messenger configuration.