How to Restore Poison Messages (or Dead Letters) from Memphis broker vs RabbitMQ
Tags: memphisdev, rabbitmq, messagebrokers, deadletters
Published: 2022-09-22 16:39:59

In this article, you will understand possible the causes of poison messages in a message broker. You will also learn how to restore them in both Memphis.dev and RabbitMQ.

## Table of Contents

* What is Service Oriented Architecture?
* What is a message broker?
* About Memphis.dev
* About RabbitMQ
* What are poison messages?
* How to Restore Poison Messages from Memphis 
* How to Restore Poison Messages from RabbitMQ 
* Summary

## What is Service Oriented Architecture?

Service Oriented Architecture (SOA) involves splitting different parts of your application into services. That way you can have services for Authentication, Batch-Processing, Managing Videos, Notifications, etc.

Separating core functionalities into services help your platform to scale appropriately. You also get to enjoy the benefit of the whole system not breaking down when one service malfunctions. In addition, you can improve each service without tampering others.

Such backend features usually carry out asynchronous tasks. These asynchronous activities don’t complete at the same time. If a user is interacting on your platform, you don’t want the user to wait for long processes to complete. You want to continue interactivity. At the same time, you want a service to tell you when it is done with its job. This is where message brokers come in. 

## What is a message broker?

A message broker is a communication tool used within service-oriented platforms. A message broker (or message queue) is a man-in-the-middle for services in your backend. 

With message brokers, some services can send messages to the broker whereas some can receive. Services that send messages are called _producers_. On the other hand, services that receive messages are called _consumers. _

A “message” in message broker stands for _what_ the service sends. It could be an event, piece of data, a task, etc. A “message” represents what the services communicate. 

As a [broker](https://en.wikipedia.org/wiki/Broker), a message broker is an intermediary for messages. As a [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)), a message broker permits you to stock up messages till consumers get them. Message brokers complete the scaling of Service Oriented platforms. Message brokers ensure that services _appropriately_ communicate with each other. 

[Memphis.dev](https://memphis.dev) and [RabbitMQ](https://www.rabbitmq.com/) are example message brokers. They are both open source. Both Memphis.dev and RabbitMQ have a dashboard (or UI) to visualise and manage “messages”.

You can install both of them on any Operating System with [Docker.](https://docker.com/) If you don’t have Docker installed, [install Docker for your Operating System here](https://docs.docker.com/desktop). 

![Screenshot of Docker Desktop showing Memphis and RabbitMQ containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5dkksyszphf1mzw5rca.png)

## About Memphis.dev

Memphis.dev is a real-time data processing platform. It is an open-source, real-time data processing platform for in-app streaming use cases.

Run the following command in your terminal to install Memphis. 


```
curl -s https://memphisdev.github.io/memphis-docker/docker-compose.yml -o docker-compose.yml && docker compose -f docker-compose.yml -p memphis up
```


When the command completes, open `localhost:9000` in the browser, it should show you the Memphis UI. Execute the following steps to get started:



* Enter your email, full name, and set a password. Your email address is important for account recovery. Create account.
* Enter `hello` for both factory and station names. These are just for demo purposes. Click Next.
* Enter `hello` for app username. Click next.
* Click or skip through the next welcome onboarding code and steps. Then launch the dashboard.



![Screenshot of Memphis dashboard after setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f51o214tdbxzgufwc1vw.png)

## About RabbitMQ

RabbitMQ is the most widely deployed open source message broker. With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers. 

Run the following command in your terminal to install RabbitMQ. 


```
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management
```


When the command completes, open `localhost:15672` in the browser, it should show you the RabbitMQ UI. Sign in with `guest` as both username and password. These are the default credentials when you set up RabbitMQ with Docker. 



![Screenshot of RabbitMQ dashboard after setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo9gmbao3dsy0q5u5ak0.png)

## What are poison messages?

Poison messages are those messages that have not reached their destination. The message broker had received them but it found it hard to deliver them. At times, it could not deliver them because the consumer may be offline. So the broker will keep sending the messages till they finally become _poison_. 

Another reason why poison messages could occur is because of acknowledgment. In a normal setup, when a consumer receives and uses up a message from the broker, the consumer _acknowledges_ that message. Acknowledging a message tells the broker that that message has been received. Acknowledging is important. It is the broker’s way of ensuring proper communication across services. 

Asides acknowledging, a consumer can reject a message (negative acknowledgment). When a consumer receives a message and rejects it or does not acknowledge it, the message broker will keep on retrying. It keeps retrying because it wants to ensure that the message gets across to the consumer. After all, that’s why it is a broker in the first place! 

Also, there is usually a predefined number of times a broker would keep retrying messages before those messages become poison. You can also set this “number of times”. Also, you can instead configure a message broker to keep retrying but for a _period of time_ (like 5mins, 2hours, 7days, etc.) 

We call these failed messages _poison_ because they introduce unintended effects to the system. If it was a user-initiated task, this user may never get the intended value delivered. If the value were a paid feature, such failure could cause a terrible customer experience (and loss of money). On the broker’s side, it could reduce platform fidelity. As it could seem that the message broker is not reliable for its job.

Poison messages are also called dead letters. “Dead letters” suit properly for poison messages for two reasons:

1. Messages are letters in the context of the broker’s transactions. A letter has a destination likewise a message.
2. The letters are “dead” because they did not reach their appropriate destination. 

Rather than discarding failed or unacknowledged messages, message brokers keep retrying as stated above. If retry time or number of retries exceed the preset maximum, message brokers store these poison messages (or dead letters). You then need to restore these poison messages to ensure smooth running of your system. How you restore poison messages (or dead letters) depend on the message broker you are using.

## How to Restore Poison Messages from Memphis 

To restore poison messages in Memphis, resend the poison messages and acknowledge them. 

To see this in action, let’s use NodeJS to create a simple “Hello World” usecase of Memphis. We will write code that sends and receives “Hello World” messages to and from Memphis. However, the consuming part will not acknowledge the messages. This way Memphis will keep sending till they become poison messages. 

Create a new `memphis-poison` folder, install memphis, and run the code for the above setup. Run the following commands in your terminal to do the above.


```
mkdir memphis-poison 
cd memphis-poison 
npm init -y
npm install --save memphis-dev
```


Create a new `index.js` file inside the newly created `memphis-poison` folder. Paste the following JavaScript code inside.


```js
const memphis = require('memphis-dev');

(async function () {
  try {
    await memphis.connect({
      host: 'localhost',
      username: 'hello',
      connectionToken: 'memphis'
    });

    const consumer = await memphis.consumer({
      stationName: 'hello',
      consumerName: 'hello_consumer1',
      consumerGroup: 'hello_consumer_group',
    });
    consumer.on('message', (message) => {
      console.log(message.getData().toString());

      // commented out the following line to produce poison messages
      message.ack();
      
    });
    consumer.on('error', (error) => {
      console.log(error);
    });

    const producer = await memphis.producer({
      stationName: 'hello',
      producerName: 'hello_producer1'
    });
    for (let index = 0; index < 10; index++) {
      await producer.produce({
        message: Buffer.from('Hello world')
      });
      console.log('Message sent');
    }
    console.log('All messages sent');
  } catch (ex) {
    console.log(ex);
    memphis.close();
  }
})();
```


The above code connects to Memphis broker. It produces 10 “Hello World” messages and consumes them. Run the file by running the following command in the terminal. Ensure you are within the `memphis-poison` folder inside the terminal. 


```bash
node index.js
```


Notice that the terminal keeps receiving the “Hello World” messages. This is because the   `message.ack();` line is commented out. That code is supposed to acknowledge the messages but as it is commented out, the messages become poison messages. 

The default Time-To-Live (TTL) for messages in Memphis is 30 seconds. So the code will keep receiving “Hello World”s for up to half a minute before it stops and becomes poison. 

Check the Memphis UI dashboard to see the poison messages. It will create a Dead Letter Station (DLS) and keep the poison messages there. 



![Screenshot of Memphis dashboard showing poison messages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7fuicrdzr4f2uhagjc2d.jpg)


To restore the poison messages, simply restore and acknowledge them. 

* Select all of the poison messages and click on resend.
* Get back to the code, uncomment the `message.ack();` line, and re-run the file. 

This will re-produce 10 “Hello World”s, then consume them, and also restore the poison messages from the previous run. And that’s how to restore poison messages from Memphis broker.

## How to Restore Poison Messages from RabbitMQ 

To restore poison messages in RabbitMQ, create a Dead Letter Exchange (DLX) for those messages.

To see this in action, let’s use NodeJS to create a simple “Hello World” use case of RabbitMQ. We will write code that sends and receives “Hello World” messages to and from RabbitMQ. However, the consuming part will negatively acknowledge the messages. This way RabbitMQ will keep sending till they become poison messages. 

Create a new `rabbitmq-poison` folder, install rabbitmq, and run the code for the above setup. Run the following commands in your terminal to do the above.


```
mkdir rabbitmq-poison 
cd rabbitmq-poison 
npm init -y
npm install --save amqplib
```

Create a new `index.js` file inside the newly created  `rabbitmq-poison` folder. Paste the following JavaScript code inside.

```js
const amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function (error0, connection) {
  if (error0) throw error0;

  const queue = 'hello';
  const msg = 'Hello World!';

  connection.createChannel(function (error1, channel) {
    if (error1) throw error1;

    channel.assertQueue(queue, {
      durable: false,
      messageTtl: 10000
    });

    for (let index = 0; index < 10; index++) {
      channel.sendToQueue(queue, Buffer.from(msg));
      console.log(` [*] Sent ${msg}`);
    }
    console.log('All messages sent');

    console.log(` [*] Waiting for messages in ${queue}. To exit press CTRL+C`);
    let receiveCount = 1;
    channel.consume(queue, (msg) => {
      console.log(` [${receiveCount}] Received ${msg.content}`);
      receiveCount++;

      // the following negatively acknowledges the message to make it poison
      channel.nack(msg);
    });
  });
});
```

The above code connects to RabbitMQ broker. It produces 10 “Hello World” messages and consumes them. 

In `channel.assertQueue`, we’ve set the `messageTtl`  (Messages’ Time To Live - TTL) to 10000 (10 seconds actually). We are setting this so that RabbitMQ can stop retrying after 10 seconds. RabbitMQ doesn’t set a default TTL. So we can say that RabbitMQ’s default TTL for messages is infinity. We are setting this TTL to ensure that the messages quickly become poison messages.

In `channel.consume`, we are keeping count of how many times the consumer received messages from RabbitMQ. The `receiveCount` quickly gets huge under 10 seconds and finally stops counting (the consumer stops receiving). It gets this high to tell how much RabbitMQ keeps retrying messages to ensure they get delivered. 

Still in `channel.consume`, the  `channel.nack(msg);` line causes the messages to be negatively acknowledged. Given that the messages have not been acknowledged and that they have a 10-second time-to-live, they become poison messages. 

Run the file by running the following command in the terminal. Ensure you are within the `rabbitmq-poison` folder inside the terminal.


```
node index.js
```



To restore the poison messages, create a Dead Letter Exchange (DLX) for those messages. To create this DLX, you will need to setup a different channel for consuming these dead letters (or poison messages). This DLX will consume the dead letters immediately after they get negatively acknowledged. 

Create a different `dlx.js` file within the `rabbitmq-poison` folder. Paste the following code inside


```js
const amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function (error0, connection) {
  if (error0) throw error0;

  const queue = 'helloDlx';
  const msg = 'Hello World!';

  connection.createChannel(function (error1, channel) {
    if (error1) throw error1;

    channel.assertQueue(queue, { deadLetterExchange: 'dlx' });

    let sendCount = 1;
    const interval = setInterval(() => {
      channel.sendToQueue(queue, Buffer.from(msg));
      console.log(` [${sendCount}] Sent ${msg}`);

      if (sendCount > 10) {
        clearInterval(interval);
        console.log('All messages sent');
      } else {
        sendCount++;
      }
    }, 500);

    console.log(` [*] Waiting for messages in ${queue}. To exit press CTRL+C`);
    let receiveCount = 1;
    channel.consume(queue, (msg) => {
      console.log(` [${receiveCount}] Received ${msg.content}`);
      receiveCount++;

      // makes poison messages and send them to the dead letter exchange above
      channel.nack(msg);
    });
  });

  connection.createChannel(function (error2, channel) {
    if (error2) throw error2;

    channel.assertExchange('dlx', 'direct');

    let receiveCount = 1;
    channel.consume(queue, (msg) => {
      console.log(` [${receiveCount}] Restored Poison ${msg.content}`);
      receiveCount++;
    });
  });
});
```


The above code connects to RabbitMQ broker. It uses a different queue (helloDlx) from the queue in `index.js` (hello) to show the restoring process. The main difference between this code and the code `index.js` is that this code introduces the Dead Letter Exchange (DLX). 

In this example, the name of the DLX is simply `'dlx'`. You should use more meaningful DLX names in your codebases. DLX are backup kind of services to ensure that the poison messages get properly handled. 

When creating the DLX (inside the second channel), we used `channel.assertExchange('dlx', 'direct');`. However, when setting the primary queue for the messages (inside the first channel), we indicated the DLX in the options parameter with `channel.assertQueue(queue, { deadLetterExchange: 'dlx' });`.

The above code also sends a message every 0.5 seconds till they reach 10 messages. This speed reduction is to see the restoring process in action. 

Run this `dlx.js` file in your terminal:


```
node dlx.js
```


This consumes the dead letter messages immediately. Notice that the “Hello World” messages get restored after their first negative acknowledgment.



## Summary

To restore poison messages in Memphis, simply resend and acknowledge them. To restore poison messages in RabbitMQ, setup a dead letter exchange for them. 

The manner of creating poison messages in this article was mainly for demo purposes. In real life, you will encounter poison messages from unforeseen circumstances. Consuming services could get faulty and hence cause unacknowledged messages. Either way, you’ve learned how to restore poison messages from Memphis broker and RabbitMQ.

Cheers!


## Helpful Resources

* [Dead Letter Station (DLS) - Memphis Docs](https://docs.memphis.dev/memphis-new/dashboard-ui/troubleshooting/dead-letter)
* [Dead Letter Exchanges (DLX) - RabbitMQ Docs](https://www.rabbitmq.com/dlx.html)