FastAPI Performance Bottlenecks: Identifying Blockers with Distributed Tracing

Introduction FastAPI has become the go‑to framework for Python microservices, yet many teams stumble over hidden performance bottlenecks. In the first 100 words you’ll see how FastAPI Performance Bottlenecks can be uncovered with distributed tracing, allowing you to cut latency, lower MTTR, and keep SLA commitments. This guide walks you through a complete project—from idea to launch—highlighting milestones, deliverables, and decision points at each stage.


Project Planning: Defining Metrics and Success Criteria

Identify Business‑Critical Endpoints

  • List the top‑3 API routes that directly affect revenue (e.g., /checkout, /login, /search).
  • Establish baseline latency (p50, p95) using a simple load test tool like locust.

Set Observability Goals

What is Distributed Tracing? Distributed tracing records the path of a request as it travels through services, creating a timeline of spans. By visualising each span, engineers can pinpoint where time is spent, isolate slow database calls, or detect thread‑blocking I/O. This insight reduces mean time to resolution (MTTR) by up to 40 % in many real‑world deployments.

  • Target MTTR reduction of 30 %.
  • Aim for 99.9 % SLA compliance on latency.

Choose the Stack

  • FastAPI (Python 3.11)
  • PostgreSQL with async driver asyncpg
  • Redis for caching
  • Lescopr APM for tracing and dashboards

Observability best practices – internal link placeholder.


Implementation: Instrumenting FastAPI with Tracing

1. Add Lescopr Tracing Middleware

from lescopr import trace

app = FastAPI()
app.add_middleware(trace.TracingMiddleware, service_name="order-service")
  • This injects a trace ID into every incoming request.
  • All downstream calls automatically become child spans.

2. Convert Blocking I/O to Async

  • Replace synchronous requests calls with httpx.AsyncClient.
  • Switch from psycopg2 to asyncpg for non‑blocking DB access.

3. Enrich Spans with Context

@router.get("/checkout")
async def checkout(order: Order):
    with trace.span("validate_order"):
        await validate(order)
    with trace.span("reserve_inventory"):
        await reserve(order)
    return {"status": "ok"}
  • Adding custom spans isolates each logical step.

4. Deploy to Staging

  • Use Docker Compose with a Lescopr collector container.
  • Verify that trace data appears in the Lescopr UI.

Analysis: Detecting Bottlenecks via Trace Data

Visualising the Trace Graph

  • Open the Lescopr dashboard → Traces → filter by service_name=order-service.
  • Look for spans that exceed the p95 latency threshold.

Common FastAPI Anti‑Patterns

  1. Synchronous DB connections – each request opens a new socket, causing thread contention.
  2. Unpooled HTTP calls – external APIs are called sequentially.
  3. Heavy CPU work in the event loop – blocking computations freeze async tasks.

Example Trace Breakdown

Span Avg Duration % of Total
checkout (root) 850 ms 100 %
validate_order 120 ms 14 %
reserve_inventory 620 ms 73 %
payment_gateway 110 ms 13 %

The reserve_inventory span dominates latency, indicating a database bottleneck.


Optimization: Refactoring and Verifying Improvements

Step 1: Connection Pooling

import asyncpg
pool = await asyncpg.create_pool(dsn=DB_URL, min_size=5, max_size=20)
  • Re‑using connections cuts DB handshake time by ~30 %.

Step 2: Parallelise External Calls

async def fetch_prices():
    async with httpx.AsyncClient() as client:
        price_a = client.get("https://api.pricing/a")
        price_b = client.get("https://api.pricing/b")
        return await asyncio.gather(price_a, price_b)
  • Parallel HTTP requests reduce overall response time.

Step 3: Offload CPU‑Intensive Work

  • Move heavy calculations to a background worker (e.g., Celery) and return a task ID.

Verify with New Traces

  • Re‑run the load test and compare the trace table. Expect the reserve_inventory span to drop from 620 ms to under 200 ms.
  • SLA compliance should now sit comfortably above 99.9 %.

Launch and Monitoring: Continuous Observability

Deploy to Production

  • Use Kubernetes with a Lescopr sidecar injector for automatic trace propagation.
  • Set up SLA dashboards in Lescopr to alert on latency breaches.

Ongoing Governance

  • Schedule weekly trace review meetings.
  • Adjust alert thresholds as traffic patterns evolve.

Documentation and Next Steps

To go further, Lescopr's documentation covers step-by-step setup.


Reading time: approximately 8 minutes.