Backend Frameworks (Node.js/Django)

Chapter 8
Asynchronous Processing and Real-Time Systems

Bachelors Program in Full Stack Development
Backend Frameworks (Node.js/Django)   •   Bachelor

Asynchronous Processing and Real-Time Systems

Introduction

Modern backend applications must remain responsive while performing operations whose completion time is uncertain. Database queries, remote API calls, file processing, email delivery, artificial intelligence inference, and payment authorization may take milliseconds or minutes. If a server waits synchronously for every operation, it wastes resources and can become unavailable under load.

Asynchronous programming allows useful work to continue while an operation is waiting. Background task systems move long-running or failure-prone work outside the request–response path. Real-time transports such as WebSockets allow servers to deliver updates without repeated client polling. Together, these mechanisms improve responsiveness, scalability, and user experience—but they also introduce ordering, duplication, retry, consistency, and observability challenges.

This chapter examines asynchronous behavior in Node.js and Django, task queues and message brokers, schedulers, event-driven architecture, WebSockets, idempotency, retries, and failure handling.

Learning Objectives

After completing this chapter, students should be able to:

  1. Distinguish asynchronous execution, concurrency, and parallelism.
  2. Apply promises and async/await correctly in Node.js.
  3. Explain the Node.js event loop and identify blocking operations.
  4. Build asynchronous Django views under ASGI.
  5. Select appropriate background-job, broker, and scheduling technologies.
  6. design idempotent tasks for at-least-once delivery.
  7. Implement retry, timeout, dead-letter, and failure-recovery policies.
  8. Design WebSocket-based real-time services with authentication and backpressure.
  9. Apply event-driven patterns such as transactional outbox and publish–subscribe.
  10. Evaluate operational, ethical, security, and governance implications.

1. Asynchrony, Concurrency, and Parallelism

These concepts are related but not interchangeable.

  • Synchronous execution requires one operation to complete before the next operation proceeds.
  • Asynchronous execution allows a program to suspend an operation and resume it after an external event or result becomes available.
  • Concurrency means multiple tasks make progress during overlapping periods.
  • Parallelism means multiple tasks execute simultaneously on different processor cores or machines.

A single-threaded event loop can support substantial concurrency when tasks spend most of their time waiting for network or storage I/O. It does not automatically provide parallel execution of CPU-intensive JavaScript.

1.1 I/O-bound and CPU-bound work

I/O-bound tasks spend most of their duration waiting for external systems. Examples include database queries, HTTP requests, and reading files. CPU-bound tasks spend most of their duration computing, as in image transformation, encryption, compression, and large numerical calculations.

Work typeSuitable approach
Short asynchronous I/OEvent loop and async/await
Long-running I/O workflowBackground task queue
CPU-intensive JavaScriptWorker threads or separate processes
Scheduled maintenanceScheduler plus task queue
Immediate bidirectional updatesWebSockets
Durable integration eventsBroker or event log

Moving CPU-intensive work into an asynchronous function does not make it non-blocking. If the function performs a large synchronous computation, it still monopolizes the event-loop thread.

2. Promises and async/await in Node.js

A JavaScript Promise represents an eventual result. It has one of three states: pending, fulfilled, or rejected. Once settled, its state cannot change.

function loadUser(id) {
  return database.findUser(id)
    .then(user => {
      if (!user) throw new Error("User not found");
      return user;
    });
}

The async/await syntax expresses promise-based operations in a sequential style:

async function loadUser(id) {
  const user = await database.findUser(id);
  if (!user) throw new Error("User not found");
  return user;
}

An async function always returns a promise. A returned value becomes a fulfilled promise, while a thrown exception becomes a rejected promise.

2.1 Sequential and concurrent awaiting

The following operations execute sequentially:

const profile = await getProfile(userId);
const orders = await getOrders(userId);

If neither result depends on the other, they can start concurrently:

const [profile, orders] = await Promise.all([
  getProfile(userId),
  getOrders(userId)
]);

Promise.all() rejects when any input promise rejects. It is suitable when every result is required. Promise.allSettled() waits for all inputs and reports each outcome independently, making it useful for best-effort batch processing.

Unbounded concurrency can overwhelm a database or remote API. Production systems should impose limits through connection pools, semaphores, rate limiters, or bounded job queues.

2.2 Error handling and cleanup

async function generateReport(id) {
  const connection = await pool.acquire();
  try {
    return await buildReport(connection, id);
  } catch (error) {
    logger.error({ error, id }, "Report generation failed");
    throw error;
  } finally {
    connection.release();
  }
}

Errors should be caught where they can be handled meaningfully. Silently swallowing a rejection hides failures and can leave callers waiting for outcomes that will never arrive.

2.3 Timeouts and cancellation

A timeout bounds how long a caller is willing to wait. Cancellation should also be propagated to the underlying operation where supported.

async function fetchWithDeadline(url, timeoutMs = 3000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    clearTimeout(timer);
  }
}

A timeout does not prove that the remote operation was cancelled. For example, a payment provider might complete a charge after the caller times out. Such operations require idempotency keys and later reconciliation.

3. Node.js Concurrency Behavior

Node.js normally executes JavaScript callbacks on one event-loop thread. Its runtime delegates many network operations to operating-system facilities and some file, DNS, compression, and cryptographic work to a libuv-managed thread pool.

The event loop progresses through phases that process timers, I/O callbacks, polling, setImmediate() callbacks, and close callbacks. Promise reactions use the microtask queue. Node.js also maintains a process.nextTick() queue, which is processed before ordinary promise microtasks after the current operation. Excessive microtask or nextTick scheduling can starve I/O.

Developers should not build business correctness around subtle callback ordering. Ordering can depend on execution context and runtime version.

3.1 Blocking the event loop

app.get("/hash", (req, res) => {
  const result = expensiveSynchronousHash(req.query.value);
  res.json({ result });
});

While the hash runs, that process cannot execute other JavaScript request handlers. Appropriate alternatives include:

  • worker threads for CPU-intensive JavaScript;
  • a separate background worker service;
  • asynchronous native APIs;
  • multiple processes or containers for horizontal capacity.

Worker threads offer parallelism within a Node.js application but introduce message-transfer, synchronization, and lifecycle costs. Multiple processes isolate failures more strongly but do not share ordinary in-memory state.

4. Django Asynchronous Capabilities

Django supports asynchronous request handling through the Asynchronous Server Gateway Interface (ASGI). An asynchronous view is declared with async def:

import httpx
from django.http import JsonResponse

async def exchange_rate(request):
    async with httpx.AsyncClient(timeout=3.0) as client:
        response = await client.get("https://rates.example/api/latest")
        response.raise_for_status()
    return JsonResponse(response.json())

An ASGI server such as Uvicorn, Daphne, or Hypercorn can keep many I/O-bound requests in progress without dedicating one thread to each waiting request. Under WSGI, Django can call asynchronous views, but the deployment does not obtain the complete long-lived connection and asynchronous-stack benefits of ASGI.

4.1 ORM access and synchronous boundaries

Modern Django versions provide asynchronous ORM operations, including methods such as aget(), acreate(), and asynchronous queryset iteration:

async def active_users(request):
    names = []
    async for user in User.objects.filter(active=True):
        names.append(user.username)
    return JsonResponse({"users": names})

Not every library or Django operation is asynchronously safe. A synchronous blocking function can be adapted with sync_to_async:

from asgiref.sync import sync_to_async

@sync_to_async
def perform_transaction(order_id):
    with transaction.atomic():
        # Synchronous transactional database work
        return update_order(order_id)

Developers must consult the documentation for the deployed Django version. In versions where transaction blocks are not supported directly in asynchronous mode, transactional work should remain in a synchronous function called through sync_to_async. Disabling Django's asynchronous-safety protection is not a legitimate substitute for safe adaptation.

4.2 Appropriate use

Async Django is valuable for concurrent external API calls, streaming, long polling, and WebSockets through ASGI-compatible components such as Django Channels. It does not make slow SQL efficient or CPU-heavy Python parallel. Query optimization, caching, worker processes, and task queues remain necessary.

5. Background Jobs and Task Queues

A request should usually return quickly when work is slow, retryable, or independent of the immediate response. The API can validate the request, record durable state, enqueue work, and return HTTP 202 Accepted with an operation identifier.

Asynchronous request processing architecture Client API Database Message Broker Worker Status/Event POST request 202 Accepted + operation ID Save operation Publish job Deliver Update Completion event Push update or later query
Correct request and background task flow with 202 response from API to client
Figure: Correct request and background task flow with 202 response from API to client

Figure: The client sends a request to the API, while the 202 Accepted response correctly flows from the API back to the client.

A 202 response means that processing has been accepted, not that it has succeeded. The operation resource should expose states such as pending, running, succeeded, failed, or cancelled.

5.1 Brokers and workers

A producer publishes a message to a broker. A worker consumes it, performs the task, and acknowledges completion. Common technologies include RabbitMQ, Redis-backed queues, cloud queue services, Kafka, Celery, RQ, BullMQ, and cloud-managed task systems.

MechanismTypical strengthImportant caution
RabbitMQRouting and task queuesRequires broker operations and durable configuration
Redis-backed queueSimple, low-latency jobsDurability depends on queue and Redis configuration
Cloud queueManaged scaling and availabilityVisibility-timeout and vendor semantics vary
KafkaDurable ordered event streamsNot a direct replacement for every task queue
CeleryMature Python task processingDelivery and idempotency require explicit design
BullMQNode.js jobs over RedisRedis durability and duplicate handling matter

5.2 Delivery semantics

  • At-most-once: a message is processed zero or one time; loss is possible, but duplicate delivery is avoided.
  • At-least-once: a message is retried until acknowledged; duplicates are possible.
  • Effectively once: duplicates may be delivered, but idempotent processing makes repeated execution produce one logical result.

End-to-end exactly-once execution across a broker, database, and external service is generally not obtained merely by selecting a broker option. Each boundary must participate in the guarantee.

6. Idempotency and Safe Payment Processing

An operation is idempotent when repeating it with the same logical input does not create additional effects. Setting an order status to cancelled is naturally idempotent; charging a card is not.

A dangerous payment task performs a provider charge and then records success locally. If the worker crashes between those actions, the broker retries the task and may charge the customer twice.

A safe design uses a durable, unique operation record and a provider-supported idempotency key:

from celery import shared_task
from django.db import transaction

@shared_task(bind=True, autoretry_for=(TemporaryProviderError,),
             retry_backoff=True, max_retries=6, acks_late=True)
def charge_order(self, operation_id):
    operation = PaymentOperation.objects.get(id=operation_id)

    if operation.status == "succeeded":
        return operation.provider_charge_id

    # The provider durably associates this key with one logical charge.
    response = provider_charge(
        amount=operation.amount,
        currency=operation.currency,
        idempotency_key=str(operation.idempotency_key),
    )

    with transaction.atomic():
        locked = PaymentOperation.objects.select_for_update().get(
            id=operation_id
        )
        if locked.status != "succeeded":
            locked.status = "succeeded"
            locked.provider_charge_id = response.charge_id
            locked.save(update_fields=["status", "provider_charge_id"])

    return response.charge_id

The API must create PaymentOperation transactionally with a unique idempotency key and stable amount and currency. The provider must guarantee that repeated requests using that key return the original charge rather than creating another charge.

If the worker crashes after provider_charge() but before the local save, the retry sends the same key. The provider returns the existing charge, after which the worker safely records it. A reconciliation process should query unresolved operations because provider outages, indefinite timeouts, or malformed responses can still leave local status uncertain.

Holding a database lock during the remote call is usually undesirable because it creates long transactions without making the remote side effect atomic. If a provider offers no idempotency facility, a local database transaction alone cannot guarantee one remote charge. Alternatives include provider-side lookup, a controlled payment gateway, compensating refunds, and manual reconciliation.

7. Transactional Outbox and Event-Driven Patterns

A dual-write problem occurs when an application commits a database change and publishes an event as separate actions. A crash between them can leave the database updated without an event, or an event published without the intended state.

The transactional outbox pattern writes the business change and an outbox record in one database transaction. A relay later publishes unpublished outbox records. Consumers must still be idempotent because the relay can publish a record more than once.

Important event-driven patterns include:

  • Publish–subscribe: multiple consumers independently receive an event.
  • Competing consumers: workers share jobs from one queue.
  • Event notification: an event announces that state changed; consumers query details.
  • Event-carried state transfer: the event contains state needed by consumers.
  • Saga: a distributed workflow uses local transactions and compensating actions.
  • Dead-letter queue: messages that cannot be processed are isolated for diagnosis.

Events should be named as completed facts, such as OrderPlaced, rather than commands disguised as events. Schemas require versioning because producers and consumers often deploy independently.

8. Capacity, Queueing, and Backpressure

Suppose jobs arrive at average rate \lambda jobs per unit time. There are c workers, and each worker completes an average of \mu jobs per the same unit time. A basic traffic-intensity measure is:

ρ = λ ÷ (cμ)

Here, \rho is dimensionless. The model assumes comparable workers and a work-conserving queue. A necessary stability condition for this basic capacity model is \lambda < c\mu, although that condition alone does not guarantee acceptable latency or cover bursty arrivals, dependencies, or failures.

Worked capacity example

A media service receives 120 jobs per minute. Average service time is 30 seconds, so one worker completes 2 jobs per minute. With 80 workers:

ρ = 120 ÷ (80 × 2) = 0.75

Average offered load is therefore 75% of nominal capacity. Since 120 ÷ 2 = 60, more than 60 workers are required for strict average-rate stability; 61 is the smallest integer satisfying the inequality. In practice, additional headroom is required for bursts, retries, uneven job sizes, and worker failures.

For a stable system in steady state, Little's Law relates average number in the system L, average arrival rate \lambda, and average time in the system W:

L = λW

Units must be consistent. If 2 jobs arrive per second and average total time in the system is 8 seconds, then L = 2 × 8 = 16 jobs are present on average. “Time in the system” includes both queue waiting and service time.

Backpressure prevents producers from overwhelming consumers. Techniques include bounded queues, rate limits, admission control, consumer flow control, load shedding, and adaptive concurrency limits.

9. Retries and Failure Handling

Retries are appropriate for transient failures such as temporary network loss, rate limiting, or short service outages. They are inappropriate for deterministic validation failures, invalid credentials, or malformed data unless something relevant changes.

A capped exponential retry schedule can be represented as:

dk = min(dmax, d0 × 2k) + Jk

where dk is the delay before retry number k, d0 is the base delay, dmax is the cap, and Jk is a non-negative random jitter selected according to the system's policy. Here k = 0 denotes the first retry. All delay terms must use the same time unit.

For d0 = 1 second, a 20-second cap, and zero jitter, delays are 1, 2, 4, 8, 16, 20, 20 seconds. Jitter reduces synchronized retry storms.

A complete failure policy should define:

  1. which errors are retryable;
  2. maximum attempts or elapsed time;
  3. timeout per attempt;
  4. exponential backoff and jitter;
  5. idempotency behavior;
  6. dead-letter handling;
  7. alerting and operator recovery.

A circuit breaker stops sending requests after repeated failures, waits for a recovery interval, and then allows limited test requests. It protects both the caller and a struggling dependency. Bulkheads isolate resource pools so one failing dependency does not consume every connection or worker.

10. Scheduling Periodic Work

Schedulers initiate work at a future time or on a recurring timetable. Examples include Celery Beat, cron, Kubernetes CronJobs, cloud schedulers, and BullMQ repeatable jobs.

Scheduled tasks may overlap when one execution takes longer than its interval. Designers must choose among allowing overlap, applying a distributed lock, skipping the next run, or coalescing missed runs. Time zones and daylight-saving transitions make local-time schedules hazardous. Storage in UTC and an explicit business-time-zone policy are recommended.

A scheduler should ordinarily enqueue a durable task rather than perform extensive work itself. In a replicated deployment, leader election or a singleton scheduler prevents unintended duplicate scheduling, while task idempotency provides an additional safeguard.

11. WebSockets and Real-Time Delivery

HTTP request–response communication is client initiated. A WebSocket begins with an HTTP upgrade handshake and then provides a persistent, full-duplex connection over TCP. Either endpoint can send messages without opening a new HTTP request.

import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (socket, request) => {
  const user = authenticateUpgrade(request);
  if (!user) return socket.close(1008, "Unauthorized");

  socket.on("message", raw => {
    const message = JSON.parse(raw.toString());
    validateMessage(message);
    handleClientEvent(user, message);
  });

  socket.send(JSON.stringify({ type: "connected" }));
});

Django applications commonly implement WebSockets through ASGI and Django Channels. Channel layers can distribute messages across application instances.

11.1 Production concerns

  • Authenticate the upgrade and authorize every subscription.
  • Apply origin checks, message-size limits, and input validation.
  • Use heartbeat messages to detect dead connections.
  • Impose per-client rate limits.
  • Monitor buffered bytes and disconnect persistently slow consumers.
  • Define reconnection, replay, and missed-event behavior.
  • Avoid assuming global order across multiple servers or channels.

WebSocket frames are ordered on one functioning TCP connection, but reconnects and distributed processing can cause missing, repeated, or differently ordered application events. Sequence numbers and resumable event identifiers can support recovery.

Server-Sent Events are a simpler alternative when communication is primarily server-to-client. Conventional polling remains appropriate for infrequent updates and operational simplicity.

12. Illustrative Case: Real-Time Order Fulfilment

Background

This fictional case concerns Northstar Market, an online retailer. Its original API synchronously authorized payment, reserved stock, generated an invoice, and sent confirmation email before responding.

Problem

Responses frequently exceeded ten seconds. Email outages blocked checkout, duplicate submissions occasionally created duplicate payment attempts, and customers had no visibility into fulfilment progress.

Analysis

Payment authorization was essential before confirming an order, but invoice generation and email delivery did not need to delay the response. Broker redelivery meant workers could receive tasks more than once. Database updates and event publication also formed a dual-write risk.

Approach

The team introduced the following design:

  1. The checkout API required a client idempotency key and created one durable order operation per key.
  2. Payment requests used a distinct provider-side idempotency key stored with the payment operation.
  3. After initial validation, the API returned 202 Accepted and an operation URL.
  4. Order state and an OrderSubmitted outbox record were committed in one transaction.
  5. An outbox relay published events to the broker.
  6. Idempotent workers reserved inventory, generated invoices, and sent email.
  7. Temporary errors used capped exponential backoff with jitter; exhausted jobs entered a dead-letter queue.
  8. WebSocket clients received state changes, while reconnecting clients retrieved current state through HTTP using the operation identifier.
  9. A reconciliation job compared unresolved payments with provider records.

Outcome

In the case's controlled load test, the API acknowledgement time fell from several seconds to below 300 milliseconds for most requests because nonessential work left the request path. Duplicate broker deliveries did not create duplicate charges because the payment provider enforced the stored idempotency key. Queue-depth alerts exposed an undersized invoice-worker pool before user-facing deadlines were breached.

Learning Points

Asynchrony reduced request latency but did not eliminate work. Reliability depended on durable state, provider-supported idempotency, consumer deduplication, reconciliation, observability, and explicit failure policies. WebSockets improved visibility but remained a notification channel rather than the sole source of truth.

13. Observability, Security, and Governance

Asynchronous systems separate cause and effect in time and across processes. Logs should therefore include correlation IDs, operation IDs, task IDs, attempt numbers, and trace context. Useful metrics include queue depth, oldest-message age, task duration, failure rate, retry count, dead-letter count, event-loop delay, WebSocket connection count, and consumer lag.

Security controls must protect both transport and message content. Broker connections require encryption and least-privilege credentials. Sensitive payloads should be minimized because queues, logs, and dead-letter stores may retain data longer than web requests. Retention and deletion policies must comply with privacy obligations.

Automated retries can amplify harm. A defective AI inference task, notification process, or financial workflow may repeat an incorrect action at scale. Human review, spending limits, kill switches, audit trails, and model/version metadata are appropriate for high-impact systems.

Operational ownership must be explicit. Teams need runbooks for replaying dead-letter messages, reconciling uncertain outcomes, rotating broker credentials, and responding to queue backlogs. Replaying historical events should occur in a controlled environment because current consumers may interpret old schemas differently.

14. Best-Practice Checklist

  • Keep request handlers short and non-blocking.
  • Distinguish CPU-bound work from waiting on I/O.
  • Bound concurrency and propagate timeouts.
  • Use durable operation records for long-running requests.
  • Assume task delivery can be duplicated.
  • Use provider-side idempotency for non-repeatable external effects.
  • Apply transactional outbox for database-to-broker consistency.
  • Retry only transient failures and add jitter.
  • Route exhausted tasks to an observable dead-letter process.
  • Treat WebSockets as a delivery channel, not the authoritative datastore.
  • Monitor queue age and end-to-end latency, not only queue length.
  • Test worker crashes at every boundary between side effects and state updates.

Chapter Summary

Asynchronous systems improve responsiveness by allowing applications to progress while operations wait. Node.js supports I/O concurrency through its event loop, promises, and async/await, while CPU-intensive work requires worker threads or separate processes. Django provides asynchronous request handling and ORM capabilities through ASGI, although synchronous libraries and transactional boundaries require careful adaptation.

Task queues separate slow or retryable work from HTTP requests. Because common systems provide at-least-once delivery, tasks must tolerate duplicate execution. Financial operations require durable local state plus provider-side idempotency; a local status check alone cannot prevent duplicate remote effects after a crash. Transactional outbox, reconciliation, dead-letter handling, exponential backoff, and observability address common distributed failure modes.

WebSockets support low-latency bidirectional communication, but production deployments must manage authentication, slow consumers, reconnection, replay, and distributed ordering. Reliable asynchronous design ultimately depends on explicit contracts for durability, idempotency, capacity, failure recovery, and governance.

Key Terms

Asynchrony: Execution that permits work to continue while an operation awaits completion.

Concurrency: Overlapping progress of multiple tasks.

Parallelism: Simultaneous execution on multiple processing resources.

Promise: A JavaScript object representing an eventual fulfilled or rejected result.

Event loop: A runtime mechanism that schedules callbacks and reacts to completed events.

ASGI: Python interface supporting asynchronous servers, applications, and long-lived connections.

Background job: Work executed outside the immediate request–response path.

Message broker: Infrastructure that stores, routes, and delivers messages between components.

At-least-once delivery: Delivery model in which retries prevent loss but can produce duplicates.

Idempotency: Property whereby repeating an operation has one logical effect.

Idempotency key: Stable identifier used to recognize retries of the same logical operation.

Transactional outbox: Pattern that atomically stores business changes and events for later publication.

Dead-letter queue: Isolation area for messages that cannot be processed successfully.

Backpressure: Mechanism that limits producers when consumers cannot keep pace.

Exponential backoff: Retry policy in which delays grow exponentially up to a cap.

Jitter: Random variation added to retry timing to prevent synchronized retries.

Circuit breaker: Control that temporarily blocks calls to a repeatedly failing dependency.

WebSocket: Persistent full-duplex communication protocol established through an HTTP upgrade.

Reconciliation: Process of comparing systems to resolve uncertain or inconsistent outcomes.

Saga: Distributed workflow composed of local transactions and compensating actions.

Review Questions

Short Answer Questions

  1. Distinguish asynchronous execution from parallel execution.
  2. When should Promise.allSettled() be preferred to Promise.all()?
  3. Why can a CPU-intensive function block a Node.js server even if called from an async function?
  4. What does an HTTP 202 Accepted response communicate?
  5. Why should retry schedules include jitter?

Long Answer Questions

  1. Explain the Node.js event loop and evaluate its suitability for I/O-bound and CPU-bound workloads.
  2. Discuss Django's asynchronous capabilities, including ASGI, asynchronous views, ORM access, and synchronous boundaries.
  3. Compare task queues, event streams, and WebSockets in terms of purpose, durability, and delivery behavior.
  4. Explain how transactional outbox and idempotent consumers address distributed consistency problems.
  5. Develop a comprehensive failure-handling policy for a background task system.

Analytical / Application Questions

  1. Design a safe asynchronous payment workflow that remains correct when a worker crashes after the provider completes a charge but before the local database records success.
  2. A service receives 300 jobs per minute, and each worker completes 5 jobs per minute. Calculate the minimum integer worker count required for strict average-rate stability, then recommend why additional capacity may be needed.
  3. Design a real-time notification architecture for a multi-instance Django application, addressing authorization, broker use, missed events, slow clients, and reconnection.

Multiple Choice Questions with Answers

Question 1

Which statement correctly distinguishes concurrency from parallelism?

A. Concurrency is overlapping progress; parallelism is simultaneous execution on multiple processing resources.

B. Concurrency requires multiple CPU cores, while parallelism does not.

C. Concurrency applies only to I/O, while parallelism applies only to databases.

D. The terms describe exactly the same execution behavior.

Correct Answer: A. Concurrency is overlapping progress; parallelism is simultaneous execution on multiple processing resources.

Question 2

Two independent promises must both succeed before processing continues. Which JavaScript construct is most appropriate?

A. Promise.race()

B. Promise.all()

C. Promise.reject()

D. setTimeout()

Correct Answer: B. Promise.all()

Question 3

Why can CPU-intensive synchronous JavaScript reduce Node.js server responsiveness?

A. It deletes pending promises.

B. It disables the operating system scheduler.

C. It occupies the event-loop thread and delays other callbacks.

D. It converts all network requests into blocking file operations.

Correct Answer: C. It occupies the event-loop thread and delays other callbacks.

Question 4

Which deployment interface enables Django's full asynchronous request stack and long-lived connections?

A. CGI

B. WSGI only

C. ASGI

D. SQLAlchemy

Correct Answer: C. ASGI

Question 5

In the chapter's asynchronous API architecture, which direction does the HTTP 202 Accepted response travel?

A. From the worker to the broker

B. From the client to the API

C. From the database to the worker

D. From the API to the client

Correct Answer: D. From the API to the client

Question 6

What most directly prevents a duplicate external payment when a worker crashes after charging but before saving local success?

A. A longer database lock held only after the provider call

B. A stable provider-supported idempotency key reused by retries

C. Returning HTTP 200 instead of HTTP 202

D. Increasing the number of workers

Correct Answer: B. A stable provider-supported idempotency key reused by retries

Question 7

What problem does the transactional outbox pattern primarily address?

A. Atomic coordination of a database change and its event-publication intent

B. Encryption of WebSocket frames

C. Parallel execution of CPU-intensive JavaScript

D. Automatic scaling of database indexes

Correct Answer: A. Atomic coordination of a database change and its event-publication intent

Question 8

A queue receives 120 jobs per minute. Each of 80 workers completes 2 jobs per minute on average. What is the traffic intensity \rho = \lambda ÷ (c\mu)?

A. 0.50

B. 0.60

C. 0.75

D. 1.50

Correct Answer: C. 0.75

Question 9

Using dk = min(dmax, d0 × 2k) with no jitter, d0 = 2 seconds, dmax = 20 seconds, and k = 3, what is the retry delay?

A. 6 seconds

B. 8 seconds

C. 16 seconds

D. 20 seconds

Correct Answer: C. 16 seconds

Question 10

Which mechanism is specifically intended to protect a system from repeatedly calling a failing dependency?

A. Circuit breaker

B. WebSocket heartbeat

C. Promise fulfillment

D. Database normalization

Correct Answer: A. Circuit breaker

Backend Frameworks (Node.js/Django)
Chapter 8 of 14