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
requestscalls withhttpx.AsyncClient. - Switch from
psycopg2toasyncpgfor 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
- Synchronous DB connections – each request opens a new socket, causing thread contention.
- Unpooled HTTP calls – external APIs are called sequentially.
- 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_inventoryspan 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.