Real-Time Systems & Background Tasks

Chapter 9: Real-Time Systems & Background Tasks

FastAPI enables high-performance real-time communication via WebSockets and provides a mechanism for non-blocking post-response processing through Background Tasks. These tools are essential for building responsive, event-driven architectures that maintain low latency for the end-user while handling complex side-effects asynchronously.

I. WebSocket Engineering: Full-Duplex Connectivity

WebSockets provide a persistent, bi-directional communication channel over a single TCP connection. In FastAPI, WebSockets are first-class citizens that utilize the ASGI send and receive primitives to handle data frames without the overhead of HTTP headers.

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await websocket.accept() # 101 Switching Protocols handshake
    try:
        while True:
            # Low-level: Awaits raw ASGI 'websocket.receive' event
            data = await websocket.receive_text()
            await websocket.send_text(f"Message received: {data}")
    except WebSocketDisconnect:
        # Handle cleanup (remove from connection manager)
        pass

Low-Level Technical Insight: The Handshake

A WebSocket connection begins as an HTTP request with an Upgrade: websocket header. The FastAPI layer inspects the ASGI scope['type']. If it is 'websocket', the request-response lifecycle is terminated, and the TCP socket remains open. Bi-directional communication is then achieved through Binary or Text Frames as defined by RFC 6455.


II. Background Tasks: Post-Response Orchestration

BackgroundTasks allow engineers to schedule functions to execute after the HTTP response has been sent. This is ideal for side-effects that do not impact the user experience, such as sending confirmation emails, updating search indexes, or logging analytics.

ClientFastAPI App1. Send Response2. Exec BgTaskI/O / Email


III. Production Anti-Patterns

  • In-Memory WebSocket Managers: Storing active connections in a simple list[] in a multi-worker production environment. A client on Worker 1 cannot communicate with a client on Worker 2. Use Redis Pub/Sub to synchronize state across workers.
  • Long-Running Background Tasks: Using BackgroundTasks for CPU-intensive work (e.g., video transcoding). These tasks run within the worker process and can degrade HTTP throughput. Use Celery or RabbitMQ for heavy asynchronous workloads.
  • Missing Heartbeats: Failing to implement "Ping/Pong" frames. Most cloud load balancers (like AWS ALB) will kill idle WebSocket connections after 60 seconds.

IV. Performance Bottlenecks

  • Broadcast Fan-out Latency: Iterating through thousands of active connections and awaiting send_text() sequentially. Use asyncio.gather() with concurrency limits to broadcast efficiently.
  • Context Switching Overhead: High-frequency small messages (e.g., real-time mouse tracking) can overwhelm the asyncio event loop with thousands of micro-tasks per second.
  • Memory Leakage: Failing to remove disconnected clients from the manager, leading to an ever-growing list of "Zombie" sockets that the Garbage Collector cannot reclaim.