DEV Community

Cover image for The Hidden Costs of Misusing Scheduled Jobs and Messaging Systems
Tran Manh Hung
Tran Manh Hung

Posted on • Originally published at webscope.io

The Hidden Costs of Misusing Scheduled Jobs and Messaging Systems

It's a common scenario we've all faced: you've got a table and a scheduled job designed to process this table.

Below is a simplified example of what such a script might look like:

-- Declare a variable to store the ID of the row to be processed
DECLARE @rowID INT

-- Get the first row from the table to process
SELECT TOP 1 @rowID = ID
FROM TableToProcess
ORDER BY ID ASC

-- Check if a row was found
IF @rowID IS NOT NULL
BEGIN
    -- Perform processing on the selected row (You can put your processing logic here)
    -- For now, we'll add a comment to indicate where the processing happens

    -- Delete the processed row from the table
    DELETE FROM TableToProcess WHERE ID = @rowID
END
Enter fullscreen mode Exit fullscreen mode

But what happens when there's no new data to process? Without any contingency, the script will repeatedly hammer your database with fruitless calls, generating redundant traffic and potentially impacting the performance of your entire system.

Now, don't get me wrong. Scheduled jobs are not the enemy. They have a critical role in certain situations, such as scheduling regular email alerts. However, as developers, we sometimes fall into the trap of over-reliance on this tool, leading to unnecessary overhead within our systems.

So, is there a better, more efficient alternative to deal with these situations? Welcome to the realm of messaging systems.

So, What's a Messaging System?

In essence, a messaging system provides a reliable way to exchange information (in the form of messages) between applications, systems, or services. The main idea here is to enable communication between different components without them necessarily knowing about each other.

In a messaging system, producers create messages and put them onto the system. Consumers get those messages and do something with them. These systems are crucial for enabling asynchronous processing, meaning that your components don't have to wait around for other tasks to finish. This can significantly enhance performance, scalability, and reliability.

Message Flow vs Scheduled Task

Advantages

  1. Real-time Processing: Messages are processed as they arrive, leading to faster responses.
  2. Decoupling of Processes: Allows systems to be developed, upgraded, and scaled independently.
  3. Improved Scalability: Messaging systems can more easily scale to handle larger loads.
  4. Reduced Database Load: By processing data as it comes in, constant database querying can be reduced.
  5. Resilience: Messaging systems can handle failures without losing data.

Disadvantages

  1. Complexity: Messaging systems come with their own complexities that require more advanced understanding and maintenance.
  2. Handling Failures: Although resilient, handling failures, "poison messages," and ensuring message ordering can be challenging.
  3. Infrastructure Requirements: Establishing and maintaining a robust messaging system might require significant resources and expertise.

Popular Messaging System Implementations

Now that you've got a taste of a messaging system let's dive into some popular implementations: Azure Service Bus, Kafka, Azure Queue System, and RabbitMQ.

Azure Service Bus

Azure Service Bus is a fully managed service from Microsoft Azure that supports queue and topic, messaging models. It's robust, provides guaranteed message delivery, and maintains the order of messages. Here's an example of sending a message with Azure Service Bus in C#:

ServiceBusClient client = new ServiceBusClient("<connection_string>");
ServiceBusSender sender = client.CreateSender("<queue_name>");

ServiceBusMessage message = new ServiceBusMessage("Hello, World!");

await sender.SendMessageAsync(message);

Console.WriteLine($"Sent a single message to the queue: {sender.EntityPath}");
Enter fullscreen mode Exit fullscreen mode

And here is how would consumer look like

string connectionString = "<connection_string>";
string queueName = "<queue_name>";

await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
    ServiceBusProcessor processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());

    processor.ProcessMessageAsync += async args =>
    {
        string body = args.Message.Body.ToString();
        Console.WriteLine($"Received message: {body}");
        await args.CompleteMessageAsync(args.Message);
    };

    processor.ProcessErrorAsync += args =>
    {
        Console.WriteLine($"Error occurred: {args.Exception}");
        return Task.CompletedTask;
    };

    await processor.StartProcessingAsync();

    Console.WriteLine("Press any key to stop receiving messages...");
    Console.ReadKey();

    await processor.StopProcessingAsync();
Enter fullscreen mode Exit fullscreen mode

And if you start using Azure function the implementation if even shorter and simple!

[FunctionName("ServiceBusConsumer")]
public static async Task Run(
    [ServiceBusTrigger("<queue_name>", Connection = "<connection_string>")] string messageBody,
    ILogger log)
{
    log.LogInformation($"Received message: {messageBody}");

    // Process the message (you can replace this with your custom logic)

    // Note: Unlike the previous examples, the message is automatically marked as completed (removed from the queue) by the Function after successful processing.
}
Enter fullscreen mode Exit fullscreen mode

Azure Queue System

Azure Queue System is another Microsoft offering specifically for storing large numbers of messages that may be accessed from anywhere via authenticated calls. It's simpler than Azure Service Bus and perfect for light workloads. An example of sending a message with Azure Queue in C# looks like this:

QueueClient queueClient = new QueueClient("<connection_string>", "<queue_name>");
string message = "Hello, World!";
await queueClient.SendMessageAsync(message);
Enter fullscreen mode Exit fullscreen mode

Kafka

Apache Kafka, an open-source system, is built to handle real-time data feeds with high throughput and low-latency. Kafka operates in a distributed manner and can handle massive amounts of data. Here's a simple example of producing a message with Kafka using Java:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

Producer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record = new ProducerRecord<>("test", "Hello, World!");

producer.send(record);
producer.close();
Enter fullscreen mode Exit fullscreen mode

RabbitMQ

RabbitMQ is another open-source messaging system known for its robustness. It supports multiple messaging protocols, message queuing, delivery acknowledgment, flexible queue routing, and more. Here's a Python example of sending a message with RabbitMQ:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='hello')

channel.basic_publish(exchange='', routing_key='hello', body='Hello, World!')

print(" [x] Sent 'Hello, World!'")
connection.close()
Enter fullscreen mode Exit fullscreen mode

Conclusion

There you have it. Keep your system from getting bogged down by an overload of scheduled tasks. Give messaging systems a try and watch your application's performance and scalability reach new heights. Whether you go for a fully managed service like Azure Service Bus or Azure Queue System or an open-source option like Kafka or RabbitMQ, you'll reap the benefits of reliable, scalable, and high-performance messaging.

Top comments (0)