Asynchronous Processing

The fundamentals of moving an intensive task to the background

The Code Journal
CodeX
5 min readJul 2, 2021

--

How it works

The Message

Rather than awaiting the completion of the task before responding to the client, the PDF service simply receives a request, shapes it into a message, usually JSON or XML, and places it onto the queue. This message contains instructions for the consumer.

The service can then respond to the client, possibly with a message identifier that would could allow the client to keep tabs on the pending process.

The Queue

A queue can be implemented in many ways, from a single file to a feature rich message broker. Regardless of the implementation, the main responsibility is buffering and distributing async requests.

The Consumer

Once on the queue the message waits. When the time is right, a consumers will take a message from the queue and begin processing.

The when around message consumption is decided by the consumers’ execution model. These models are often chron or daemon-like. In the chron model, a consumer periodically checks the queue for messages, maybe every 5s, 5m or 5h. In the daemon model, a consumer is continuously revisiting the queue for new messages.

In addition to the various execution models, consumers can use different subscription models to declare the messages they are interested in.

Two common subscription models are the direct worker queue and pub sub.

In the direct worker queue approach, the producers and consumers only know the name of the queue. Each message enqueued by a producer is added to a single work queue located by name. One the other end of the queue are n number of consumers waiting for messages.

In the publish/subscribe model, a message can be delivered to more than one consumer. Producers push messages to a topic rather than a queue. The topic then clones the message for each subscriber/consumer and drops it in their private queue to be independently processed by a consumer.

Protocols

In order for the different components of an asynchronous system to communicate a protocol is needed. Three of the more common protocols used in async processing are AMQP, STOMP, and JMS.

AMQP(Advanced Message Queuing Protocol) is an industry standard. It is feature rich with things like reliable messaging, delivery guarantees, and transactions. But with this functionality comes a bit more overhead .

STOMP(Streaming Text-Oriented Messaging Protocol) is a stateless, text-based protocol similar to HTTP. Supporting around a dozen operations, it is a minimalist protocol whose main advantage is simplicity. But this simplicity can cause issues when more advanced functionality is needed. Any advanced features would need to be implemented as extensions using headers. This often leads to interoperability issues since there is no standard way of doing a certain thing.

JMS(Java Message Service) is a Java messaging standard with a good feature set. Unfortunately, it is purely a Java standard which makes it difficult to integrate with non-JVM(Java Virtual Machine) based tech.

Benefits

Asynchronous processing provides benefits to both the end user and the system as a whole. Here we are able to quickly clear the main thread allowing a user to continue interacting with our application while the work happens in the background. The system easily scales, since we can add more consumers to the end of the queue as the number of enqueued messages increases. If our system was to see an unexpected spike in requests it is possible tasks may be delayed, but consumers are never overwhelmed, they simply continue working thru the backlog of enqueued tasks. While processing, any failures are isolated. Failed tasks due to things like malformed messages are removed and placed in a dead-letter queue where they are handled accordingly. All of this is happening in a decoupled system where the producers and consumer know nothing about the other.

Challenges

But with these benefits come challenges that one must be ready to manage. Since we are processing messages in parallel and there is no synchronization between consumers. Async processing does not guarantee message ordering.

One way this could be managed is by limiting the number of consumers one to a queue. But, this solution does not scale. Another option may be to build our system assuming messages will arrive out of order. Lastly, we try could leveraging a message broker that supports partial message ordering.

Failures are possible within any system and an async system is no different. These failures often lead to re-enqueuing which in turn leads to unexpected results as tasks end up being executed more than once.

To handle this challenge we can build our system to expect at least one delivery rather than exactly one. But, doing this is difficult as it requires consumers to be idempotent. In some situations this could be possible, but this brings along the possibility of making our consumers more sensitive to out of order messages.

This unpredictability leads to increased complexity within asynchronous processes. Documentation is key when it comes to understanding how messages flow thru the system and creating a mental model of how the pieces fit together.

Conclusion

Asynchronous processing is dynamic, powerful and a tad unpredictable. But with a solid understanding of the limitation we can build more robust, highly available systems that improve the user experience.

--

--

The Code Journal
CodeX

Musings and microlearnings of a software engineer