Course contentsShow
← Node.js
Lesson 1231 of 1,98011. Task Queues and Background ProcessingFree lesson

Handling Message Reordering

Design systems that can process tasks out-of-order or reconstruct sequence when delivery order is not guaranteed.

In this lesson, you will learn how to design systems that maintain data integrity even when background jobs arrive out of their intended sequence.

When working with distributed systems, network jitters or parallel worker processing often mean that a "Job B" might complete before "Job A," even if "Job A" was triggered first. In a system like an e-commerce order tracker, if an "Order Cancelled" message arrives and is processed before an "Order Placed" message, your database could end up in an invalid state.

Using Versioning and Sequence IDs

The most robust strategy to handle out-of-order delivery is to include a sequence_number or a version timestamp in your payload. Instead of blindly overwriting database records, your worker should verify the state before applying changes.

For example, when updating a user's profile status, you can use a "conditional update" pattern:

async function updateProfile(userId, newStatus, version) {
  // Only update if the incoming version is greater than the stored version
  const result = await db.query(
    'UPDATE profiles SET status = $1, version = $2 WHERE id = $3 AND version < $2',
    [newStatus, version, userId]
  );

  if (result.rowCount === 0) {
    console.log(`Out-of-order or stale update detected for user ${userId}. Skipping.`);
  }
}

Buffering and Re-sequencing

If your business logic cannot ignore stale messages, you may need a "re-sequencing buffer." In this pattern, your worker checks if the arriving message is the expected next one in the sequence. If it arrives early (e.g., you received sequence #3 but are still waiting for #2), you place the task into a temporary, sorted buffer or a "waiting room" queue. Once the missing message finally arrives and is processed, you check the buffer to see if the next sequential task can now be executed.

While this adds architectural complexity, it ensures that your system remains deterministic. Always aim to design your state transitions to be idempotent—meaning that applying the same change multiple times (or in different orders) results in the same final outcome—whenever possible.

Key Takeaway: Never assume sequential delivery; instead, use versioning to ignore stale updates or implement re-sequencing buffers to enforce order at the application layer.