404 Not Found

404 Not Found


nginx

Performance Optimization — From 100 to 1 Million QPS

Performance optimization is like building a highway—you start by fixing the most congested sections (the bottlenecks), measuring the results after each section is completed, rather than widening the entire road indiscriminately. Blind optimization is a waste; measurement-driven optimization is the most efficient approach.

1. What You'll Learn


2. Alice's True Story

(1) Pain Point: 500 QPS on a single server is not enough

After PriceTracker went live, a single server could only handle 500 QPS, but during major sales events, traffic surged to 5,000 QPS, causing API response timeouts. Charlie added more servers, but the improvement was negligible—the database connection pool was exhausted, the synchronization code was blocking the event loop, and JSON serialization was consuming 40% of the CPU.

(2) A Systematic Approach to Performance Optimization

Performance optimization isn't about guesswork; it's a cycle of measurement → identifying bottlenecks → optimization → validation. We tackle issues layer by layer, from the code level (async/serialization) to the database level (indexes/connection pools) to the architectural level (Workers/load balancing).

(3) Revenue

PriceTracker's QPS has been optimized from 500 to 10,000 (on a single server), and with a load-balancing cluster, it can reach 1 million QPS, while the P99 latency has been reduced from 500 ms to 30 ms.


3. Levels of Performance Optimization

(1) Three-Layer Optimization Model

100%
graph TD
    L3[Infrastructure Layer] --> L2[Database Layer]
    L2 --> L1[Application Layer]
    
    L1 --- A1[async/await - Block detection]
    L1 --- A2[Serialization - orjson]
    L1 --- A3[Compression - GZip]
    
    L2 --- D1[Indexes - Query optimization]
    L2 --- D2[Connection Pool - Size tuning]
    L2 --- D3[Query Patterns - N+1 prevention]
    
    L3 --- I1[Workers - Gunicorn config]
    L3 --- I2[Load Balancer - Nginx]
    L3 --- I3[Auto-scaling - K8s HPA]

(2) Paths to Improving QPS

100%
flowchart LR
    A[500 QPS\nBaseline] -->|async + orjson| B[2000 QPS]
    B -->|DB indexes + pool| C[10000 QPS]
    C -->|4 Workers| D[40000 QPS]
    D -->|Nginx LB x 5| E[200000 QPS]
    E -->|K8s x 5 pods| F[1000000 QPS]
Phase Optimization Method QPS Improvement Factor
Baseline Default Configuration 500 1x
Application Layer async + orjson 2,000 4x
Database Layer Indexes + Connection Pool 10,000 20x
Workers 4 Workers + Gunicorn 40,000 80x
Load Balancing 5 Nginx instances 200,000 400x
K8s Elasticity 5 Pods Auto-scaling 1,000,000 2000x

4. Application Layer Optimization

(1) Troubleshooting Asynchronous Blocking

(1) ▶ Example: Synchronous Code That Blocks the Event Loop

PYTHON
import asyncio
import time

# BAD: sync I/O blocks the event loop
async def get_price_bad(product_id: int):
    time.sleep(0.1)  # Blocks ALL other requests for 100ms!
    return {"product_id": product_id, "price": 9.99}

# GOOD: use asyncio for I/O
async def get_price_good(product_id: int):
    await asyncio.sleep(0.1)  # Non-blocking, other requests continue
    return {"product_id": product_id, "price": 9.99}

Output:

TEXT
# Function defined successfully

(2) ▶ Example: run_in_executor wraps synchronous code

PYTHON
import asyncio
from functools import partial

# Sync function that cannot be made async
def sync_scrape_price(url: str) -> float:
    # Uses requests library (sync only)
    import requests
    response = requests.get(url, timeout=10)
    return parse_price(response.text)

async def get_price_async(product_id: int):
    loop = asyncio.get_event_loop()
    # Run sync function in thread pool - doesn't block event loop
    price = await loop.run_in_executor(
        None,  # Default thread pool
        partial(sync_scrape_price, f"https://api.example.com/prices/{product_id}"),
    )
    return {"product_id": product_id, "price": price}

Output:

TEXT
# Function defined successfully

(2) JSON Serialization Optimization

(3) ▶ Example: Using orjson instead of json

PYTHON
# Install: uv add orjson
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)

@app.get("/api/v1/products")
async def list_products():
    # orjson is 3-10x faster than stdlib json
    return {"products": [{"id": i, "name": f"Product {i}"} for i in range(100)]}

Output:

TEXT
# Function defined successfully
Serialization Library Speed Description
json (standard library) Benchmark Pure Python implementation
orjson 3-10x Implemented in Rust, automatically handles datetime
ujson 2-3x C implementation, some compatibility issues

(4) ▶ Example: GZipMiddleware Compression

PYTHON
from fastapi import FastAPI
from starlette.middleware.gzip import GZipMiddleware

app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000)  # Compress responses > 1KB

@app.get("/api/v1/products")
async def list_products():
    # Large JSON responses are automatically compressed
    return {"products": [...]}  # 50KB → ~5KB with gzip

Output:

TEXT
# Function defined successfully

5. Database Layer Optimization

(1) Indexing Strategy

(1) ▶ Example: PriceTracker Index Design

PYTHON
from sqlalchemy import Index

class Product(Base):
    __tablename__ = "products"
    __table_args__ = (
        # Single-column indexes for common filters
        Index("ix_products_category", "category"),
        Index("ix_products_created_at", "created_at"),
        # Composite index for category + price range queries
        Index("ix_products_category_base_price", "category", "base_price"),
        # Partial index for active products only (PostgreSQL specific)
        Index("ix_products_active", "created_at", postgresql_where=("deleted_at IS NULL")),
    )
    ...

Output:

TEXT
# Execution Successful
Index Type Suitable Queries Example
Single-column index Equi/range filter WHERE category = ?
Composite Index Multiple Condition Combinations WHERE category = ? AND price > ?
Partial Index Conditional Subset WHERE deleted_at IS NULL
Covering Index Avoid Table Lookup INCLUDE (name, price)

(2) Connection Pool Tuning

(2) ▶ Example: Production-Level Connection Pool Configuration

PYTHON
engine = create_async_engine(
    DATABASE_URL,
    pool_size=25,           # Persistent connections per worker
    max_overflow=10,        # Extra connections when pool exhausted
    pool_timeout=30,        # Wait time for available connection
    pool_recycle=1800,      # Recycle connections after 30 min
    pool_pre_ping=True,     # Test connection before use
    echo=False,             # Disable SQL logging in production
)

Output:

TEXT
# Execution Successful
Parameter Recommended Value Description
pool_size CPU cores x 2 + 1 Basic connection count
max_overflow pool_size x 0.5 Maximum number of burst connections
pool_timeout 30s Timeout
pool_recycle 1800s Prevent MySQL/PG from closing idle connections
pool_pre_ping True Prevent use of disconnected connections

6. Concurrency Model Optimization

(1) Workers Configuration

(1) ▶ Example: Gunicorn + Uvicorn Workers

BASH
# Production: Gunicorn manages Uvicorn workers
gunicorn app.main:app \
    --workers 9 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \
    --graceful-timeout 30 \
    --access-logfile - \
    --error-logfile -

Output:

TEXT
# Command executed successfully
DOCKERFILE
# In Dockerfile
CMD ["gunicorn", "app.main:app", \
     "--workers", "9", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000"]
Configuration Formula/Value Description
Number of Workers (2 x CPU) + 1 4 cores = 9 workers
worker-class UvicornWorker ASGI worker
timeout 120s Worker killed due to timeout
graceful-timeout 30s Graceful shutdown timeout
max-requests 10,000 Automatic restart to prevent memory leaks

❓ FAQ

Q How do you identify performance bottlenecks?
A Use a toolchain—cProfile to analyze CPU hotspots, py-spy for real-time sampling, Prometheus to monitor latency distributions, and slow query logs to analyze the database. Measure first, then optimize.
Q How large is the thread pool for run_in_executor?
A The default is min(32, os.cpu_count() + 4). For CPU-intensive tasks, use ProcessPoolExecutor to avoid GIL limitations.
Q Is orjson compatible with Pydantic?
A Yes, it is. FastAPI's default_response_class=ORJSONResponse automatically serializes Pydantic models using orjson to generate model_dump() output.
Q How do I balance the connection pool and the number of workers?
A Each worker has its own connection pool. 9 workers x 25 connections = 225 database connections. The default max_connections setting in PostgreSQL is 100; you'll need to increase or decrease the pool_size accordingly.
Q Does GZip compression come with a performance overhead?
A Compression consumes CPU resources, but it reduces network transmission time. For JSON responses larger than 1 KB, the benefits of compression far outweigh the CPU overhead (the compression ratio is typically 5 to 10 times).
Q How can I verify the effectiveness of the optimization?
A Use Locust or k6 to perform load testing, and compare the QPS, P50/P95/P99 latency, and CPU/memory usage before and after the optimization. Change only one variable at a time.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Install orjson, change FastAPI's default response class to ORJSONResponse, and use curl to compare the response times for JSON and orjson. Hint: default_response_class=ORJSONResponse
  2. Advanced Exercise (Difficulty ⭐⭐): Add indexes to the products table in PriceTracker (category, created_at, and a composite index of category and base_price), and compare the execution times of the same query before and after adding the indexes. Hint: Index("ix_name", "col1", "col2") + EXPLAIN ANALYZE
  3. Challenge (Difficulty: ⭐⭐⭐): Comprehensive performance optimization—orjson serialization + GZipMiddleware + connection pool tuning + Gunicorn with 4 workers. Use Locust to run load tests and compare QPS and P99 latency before and after optimization. Hint: locust -f locustfile.py --host=http://localhost:8000

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏