System Design Fundamentals

Lesson 6 of 12 · 20 min

x
50%

Message Queues & Async Communication

Synchronous coupling creates chains of failure: if the email service is slow, the order service is slow. Message queues decouple producers from consumers — the order service publishes an event and returns immediately; the email service processes it when it can. This improves availability, absorbs traffic spikes, and enables independent scaling of each service. Kafka is the dominant choice for high-throughput event streaming. AWS SQS and RabbitMQ handle task queues — background jobs, email sending, file processing. The key design decision is whether consumers need to replay events (Kafka retains messages for days) or just process once (SQS deletes after acknowledgement). Dead-letter queues catch repeatedly failing messages so they can be inspected without blocking the main queue.

Before
Synchronous — slow email blocks order response
// User waits for ALL of these before getting a response
async function placeOrder(order: Order) {
  await db.saveOrder(order);          // 20ms
  await emailService.sendConfirm();   // 800ms (if slow)
  await inventoryService.reserve();   // 150ms
  await analyticsService.track();     // 300ms
  return { success: true };           // total: ~1,270ms
}
After
Async — order confirmed in 25ms, rest runs in background
async function placeOrder(order: Order) {
  await db.saveOrder(order);                    // 20ms
  await queue.publish('order.placed', order);   // 5ms
  return { success: true };                     // total: 25ms
}

// These run independently, in parallel, at their own pace:
queue.subscribe('order.placed', emailService.sendConfirm);
queue.subscribe('order.placed', inventoryService.reserve);
queue.subscribe('order.placed', analyticsService.track);

Key Takeaway

Publish events for everything that does not need to happen before the response — async queues turn sequential chains into parallel fans.

PreviousNext Lesson