Backend Communication Design Patterns: Synchronous and Asynchronous Workloads

Backend Communication Design Patterns:  Synchronous and Asynchronous Workloads

Introduction

In today's dynamic software landscape, understanding the nuances of asynchronous and synchronous workloads is important. This blog post delves into their pivotal roles, shedding light on how they influence performance, scalability, and the user experience.

Asynchronous and synchronous workloads are at the heart of software operations, influencing everything from response times to resource utilization. By understanding these concepts, you'll discover how to optimize your software for peak performance and seamless scalability, ensuring a stellar user experience.

In this article, we'll uncover the intricacies of asynchronous and synchronous workloads. We'll explore their impact on software performance, scalability, and user satisfaction. Join me on this journey to harness the full potential of asynchronous and synchronous workloads in your projects.

Understanding Asynchronous and Synchronous Workloads

Synchronicity boils down to the question; "can I do work while waiting?". Can I do something else while waiting for the response to the request I just sent? By Design, when computing started, everything was synchronous. Most times we find out that asynchronous workload is preferred to synchronous workload "I want the client to do something else while waiting for the response from the server."

  • Synchronous Workloads: refers to tasks or operations that occur sequentially, one after the other, in a blocking manner. In other words, each task must be completed before the next one begins. Synchronous workloads are known for being predictable and deterministic. They are typically used for tasks where the order of execution and immediate feedback are critical.

  • Asynchronous Workloads: involves tasks or operations that can be executed concurrently and independently without waiting for each other to finish. These tasks do not block the progress of the main program. Asynchronous workloads are valued for their ability to enhance responsiveness and efficiency and are commonly used for tasks where waiting for one task or operation to complete before starting another will result in performance bottlenecks or unresponsiveness.

Differences between Asynchronous and Synchronous workloads

The fundamental differences between asynchronous and synchronous workloads lie in the concepts of blocking and non-blocking operations:

Synchronous Workloads (Blocking):

  • In synchronous operations, tasks are executed one after the other, sequentially.

  • Each task must wait for the previous one to finish before it can start, this is what creates the blocking behaviour.

  • Blocking operations can lead to potential performance bottlenecks and decreased responsiveness, especially in scenarios where tasks involve waiting for external resources or time-consuming processes.

Asynchronous Workloads (Non-blocking):

  • In asynchronous operations, tasks are executed concurrently and independently.

  • Tasks do not have to wait for each other, resulting in a non-blocking behaviour.

  • This non-blocking nature enhances performance, scalability, and responsiveness, as tasks can proceed without delay, making efficient use of system resources.

  • Asynchronous operations are very useful in scenarios where tasks can be executed together (in parallel) or require waiting for external events, such as handling network requests.

Synchronous Workloads

Synchronous workloads as I said earlier, refer to tasks or operations that are executed sequentially, one after the other, in a blocking manner. In this context, “blocking” means that each task must be completed before the next one begins. Essentially, synchronous workloads follow a step-by-step, linear execution model.

In a synchronous workload, a program or application progresses from one task to the next, and each task must finish before the program can move on. This can sometimes lead to inefficient use of resources and can make the software less responsive, especially in situations where tasks might take a significant amount of time to complete.

Synchronous workloads are easy to understand and implement, as they follow a predictable and linear flow of execution. However, they may not be the most efficient choice for all scenarios, especially in modern software development where responsiveness and resource utilisation are critical factors.

Synchronous I/O

  • Caller sends a request and blocks: This means the code below cannot be executed until this request has been resolved.

  • The caller cannot execute any code until the request has been resolved.

  • The receiver responds and the caller unblocks.

  • Caller and receiver are always in “sync”.

Example of an OS synchronous I/O (reading a file from the disk)

  • The program asks the OS to read: If the process is being executed in the CPU, the moment the process sends an I/O to read from disk, what happens is that the CPU will kick the process out. Because it is not processing anything currently.

  • Program main thread is taken from the CPU (context switching): Since you are blocked because you are reading from the disk, I will take you out of the CPU.

  • Read completes and the program can resume execution.

Context switching simply refers to switching between tasks in the CPU.

Scenarios where synchronous processing is beneficial

Synchronous processing can be beneficial in certain scenarios in software development. Here are some situations where synchronous processing is advantageous:

  • Simplicity and Predictability: Synchronous code is often easier to read, write, and maintain because it follows a linear execution model. Developers can easily understand the flow of the program, making it a good choice for simpler applications or when code readability is a priority.

  • Real-time Systems: In real-time systems, where tasks must be completed within strict time constraints, synchronous processing can help meet deadlines more reliably because it avoids the overhead associated with managing asynchronous concurrency.

  • Debugging and Testing: Debugging synchronous code can be more straightforward since the flow of execution is predictable. It allows developers to step through code sequentially, making it easier to identify and fix issues.

  • Deterministic Behaviour: Synchronous code provides deterministic behaviour, which can be advantageous in situations where unpredictability or concurrency issues are not needed, such as in safety-critical systems.

  • Resource Conservation: In situations where resource management is crucial, synchronous processing can be advantageous. For example, when dealing with limited resources on embedded systems or devices with low computing power, synchronous code can help ensure efficient resource allocation.

Examples of synchronous operations in programming

  • Database Queries: When executing database queries synchronously, the program waits for the database to return the requested data or complete the operation. If the database query takes a long time to execute, it can block the program.

  • Blocking I/O: When reading from or writing to a file, database, or network socket in a synchronous manner, the program waits until the I/O operation is finished before proceeding. For instance, a program may halt until it receives data from a remote server over the network.

  • Serial Execution of Tasks: Running multiple tasks one after the other, where each task must be completed before the next one starts. This sequential execution is synchronous, and it's common in batch processing systems.

  • Synchronous Function Calls: Calling a function that performs a time-consuming computation in a blocking manner. For instance, if a function calculates a complex mathematical equation and doesn't return until the calculation is complete, it's a synchronous operation.

  • Rendering Graphics: In graphical applications, rendering graphics or animations can be performed synchronously. Each frame is rendered one after the other, and the application waits for each frame to complete before rendering the next.

These examples illustrate scenarios where synchronous operations can lead to potential bottlenecks and reduced responsiveness, especially in applications where concurrency and parallelism are essential for optimal performance. In such cases, asynchronous programming techniques are often preferred to improve scalability and responsiveness.

Example of a Synchronous code

const fs = require('fs')
const path = require('path')

console.log("first log");
const filePath =  path.join(__dirname, 'testing.txt')

const result = fs.readFileSync(filePath, { encoding: 'utf8', flag: 'r' })
console.log('File: ' + result);

console.log("second log");

We get the following result after running the code:

$ node sync.js 
first log
File: Reading from the file. Messi is the GOAT.
second log

From the result, we can see that the code was executed synchronously, in a sequential blocking manner. The first console.log() function is executed, the file is read from the disk and the content is printed to the console then the last console.log() function is executed. Note this, the last console.log() function will only be executed when the second console.log() function has been executed.

Asynchronous Workloads

Asynchronous workloads in software development refer to tasks or processes that don't require the program to wait for each step to complete before moving on to the next one. Instead, asynchronous operations allow the program to start a task, move on to other tasks, and then return to the original task when it's finished or when certain conditions are met.

Characteristics of Asynchronous Workloads

  • Synchronous workloads are non-blocking: Asynchronous operations don't block the main program's execution. They can be initiated and run independently while the program continues to perform other tasks.

  • Callbacks or Promises: Asynchronous operations are typically managed through mechanisms like callbacks, futures and promises, which define what should happen when the task is completed or when an event occurs.

  • Event-driven: Asynchronous operations often rely on events or triggers to determine when they should resume or complete, such as data arrival, user input, or a specific time.

  • Asynchronous workloads can execute many tasks concurrently: Multiple asynchronous tasks can be executed concurrently, making efficient use of system resources and potentially speeding up overall performance.

  • Asynchronous workloads improve responsiveness: Asynchronous processing helps maintain the responsiveness of an application. It prevents the program from freezing or becoming unresponsive while waiting for time-consuming tasks to finish.

  • Asynchronous workloads improve efficiency: Asynchronous operations are suitable for tasks that may involve waiting, such as network requests or reading/writing files. During the waiting periods, the program can handle other tasks, improving overall efficiency.

Advantages of Using Asynchronous Processing

  • Enhanced Responsiveness: Asynchronous operations prevent the application from freezing or becoming unresponsive while performing time-consuming tasks. This means that users can continue interacting with the software, and it remains interactive and snappy.

  • Parallel Execution: Asynchronous tasks can be executed concurrently, taking full advantage of modern multi-core processors. This parallelisation can significantly reduce the time it takes to complete multiple tasks, leading to faster overall performance.

  • Scalability: In scenarios where many users or requests need to be handled simultaneously, asynchronous processing is essential. It allows the system to manage multiple tasks without overloading the resources, contributing to scalability and improved user experience.

  • Handling I/O Operations: Asynchronous processing is particularly beneficial when dealing with I/O-bound operations, such as reading/writing files or making network requests. It ensures that the application doesn't waste time waiting for these operations to complete and can perform other useful work in the meantime.

  • Real-time Systems: For real-time systems where events need to be processed as they occur, asynchronous processing is crucial. It allows the system to react promptly to incoming data or events, ensuring timely responses.

  • Overall System Performance: When used strategically, asynchronous processing can significantly boost the overall performance of an application or system. Optimising task scheduling and resource management helps achieve better throughput and reduced latency.

Asynchronous processing is particularly valuable in modern software development, where users expect fast, interactive, and reliable applications.

Asynchronous I/O

  • The caller sends a request.

  • The caller can work until it gets a response

  • The caller either:

    • Check if the response is ready using epoll. Epoll is a way for the computer to keep track of many things at once. Its function is to monitor multiple file descriptors to see whether I/O is possible on any of them. It waits for something to happen then it tells the computer about it.

    • Waits for the receiver to call back when it’s done. The receiver sends a completionist queue and the caller will keep interacting with the queue to know when the work is done. E.g. io_uring in Linux.

    • Spins up a new thread that blocks. (node.js uses it ). NodeJs runs blocking codes by pushing the blocking code to the LIBUV API. The LIBUV API executes the blocking code, sends the result to the event queue then the result is put in the call stack whenever it is empty. You can see an animated tutorial here.

  • In asynchronous processing, the caller and receiver are not in sync.

Examples of Asynchronous Operations

  • Non-Blocking I/O: Non-blocking I/O operations, often used in networking and file handling, allow a program to initiate I/O operations and continue executing other tasks without waiting for the operation to complete. For example, when querying the database, non-blocking I/O allows the application to query the database and continue processing other tasks, like responding to user input or even printing a log to the console, instead of halting until the query has a response.

  • Callback Functions: Callback functions are commonly used in asynchronous operations. When a specific task is complete, a callback function is invoked to handle the result. For instance, in JavaScript, when making an asynchronous HTTP request using the fetch API, you can specify a callback function to process the response data when it becomes available.

  • Async/Await: Async/await is a modern JavaScript feature that simplifies working with asynchronous operations. It allows developers to write asynchronous code in a more synchronous-looking style. By using the async keyword in a function and the await keyword before asynchronous operations, developers can create code that reads almost sequentially, making it easier to understand and maintain.

  • Promises: Promises are a pattern used to work with asynchronous operations and handle their results. A promise represents a future value or error that will be available at some point. Promises allow developers to write more structured and maintainable asynchronous code by chaining .then() and .catch() methods to handle success and error cases, respectively.

  • Event-Driven Programming: In event-driven programming, asynchronous events trigger specific actions or callbacks. For instance, in a graphical user interface (GUI) application, user interactions like clicking a button or moving the mouse are asynchronous events. When an event occurs, the application responds by executing the associated event handler. This allows the application to remain responsive to user input while handling various events concurrently.

  • Message Queues: Message queues, like RabbitMQ or Apache Kafka, enable asynchronous communication between different components or services in a distributed system. Messages are placed in a queue and processed asynchronously by consumers, allowing for efficient handling of tasks and events.

  • Database Queries: Asynchronous database queries are used to retrieve data from databases without blocking the main application thread. This allows an application to continue serving user requests while waiting for database responses.

  • Multithreading: In multithreading, multiple threads of execution run concurrently within a single process. Each thread can perform asynchronous tasks independently. This is particularly useful for tasks that can be executed together, such as data processing or rendering in video games.

  • Web Sockets: Web sockets provide a full-duplex, bidirectional communication channel over a single TCP connection. They enable real-time, asynchronous communication between a web browser and a server, making them suitable for applications like online chat or multiplayer games.

Example of an asynchronous code In NODEJS

const fs = require('fs')
const path = require('path')

console.log("first log");

const filePath =  path.join(__dirname, 'testing.txt')

fs.readFile(filePath,(err, data) => console.log(data.toString()))
console.log("second log");

We get the following result after running the code:

$ node async.js 
first log
second log
File: Reading from the file. Messi is the GOAT.

From the result, we can see that the code was executed asynchronously, in a non-blocking manner. The first console.log() function is executed, when the program gets to the line where the file is being read, it kicks it off the main thread and executes the last console.log(). When the read operation is complete, the content of the file is printed to the console.

Real-world Scenarios and Case Studies Where Synchronous Workloads Excel

  • Aviation - Air Traffic Control:

    • Case Study: Air Traffic Control Systems

      • Air traffic controllers rely on synchronous communication to ensure safe takeoffs, landings, and in-flight operations. Immediate responses are critical for managing aircraft movements.
  • IoT - Single Device Control:

    • Case Study: Smart Home Lighting

      • In a smart home system, when a user turns on a light using a mobile app, the app sends a synchronous command to the smart lighting device. The device executes the command immediately, providing real-time control over lighting.
  • Gaming - Turn-Based Games:

    • Case Study: Chess Online

      • Turn-based online games, such as chess, rely on synchronous processing. Players take turns making moves, and each move is processed synchronously. The game waits for one player to make a move before allowing the other player to respond.
  • Healthcare - Real-time Monitoring:

    • Case Study: Patient Monitoring Systems

      • In healthcare, synchronous processing is crucial for real-time patient monitoring. Devices continuously send data, such as heart rate and oxygen levels, to healthcare providers for immediate assessment.
  • Telecommunications - Voice Calls:

    • Case Study: Voice over IP (VoIP)

      • VoIP services like Skype or Zoom rely on synchronous processing to maintain real-time voice communication. When one person speaks, their voice data is transmitted and played back instantly to other participants.

Real-world Scenarios and Case Studies Where Asynchronous Workloads Excel

  • Gaming - Massive Multiplayer Online (MMO) Games:

    • Case Study: Call of Duty Mobile

      • MMO games like Call of Duty Mobile handle thousands of players in a shared virtual world. Asynchronous architecture ensures that player interactions, battles, and events happen simultaneously, providing an immersive gaming experience.
  • IoT - Real-time Data Processing:

    • Case Study: Smart City Traffic Management

      • In a smart city traffic management system, sensors at intersections continuously collect traffic data. Asynchronous processing allows the system to analyse this data in real time, adjusting traffic signals and rerouting vehicles to optimise traffic flow.
  • Cloud Computing - Serverless Computing:

    • Case Study: AWS Lambda

      • AWS Lambda, a serverless computing platform, executes functions in response to events. Asynchronous processing allows Lambda to scale automatically and handle events such as image uploads, data processing, and real-time data streaming.
  • Web Development - Concurrent User Interactions:

    • Case Study: Social Media Feeds

      • Social media platforms like Twitter or Facebook employ asynchronous processing to handle millions of users posting updates simultaneously. Each user's feed is updated independently, ensuring a seamless and responsive user experience.
  • Gaming - Web Sockets for Real-time Interaction:

    • Case Study: Online Multiplayer Game

      • Online multiplayer games rely heavily on asynchronous communication through Web Sockets. Players from around the world interact in real time, and actions like shooting, moving, or chatting occur without blocking the game's main loop.

Conclusion

Understanding synchronous and asynchronous workloads is vital in modern software development. Synchronous workloads offer simplicity and predictability, suitable for scenarios where strict order of execution and immediate feedback are crucial. Examples include air traffic control, IoT device control, and real-time healthcare monitoring.

On the other hand, asynchronous workloads excel in responsiveness, parallel execution, and scalability. They power massive multiplayer online games, serverless computing, and social media interactions. Choosing between them depends on your project's specific requirements, and mastering both equip you to tackle diverse software challenges in our evolving digital landscape.