CSC3119 Β· Module 3 of 9

API Engineering with FastAPI, Pydantic & Async Workers

Build ultra-low latency, strongly typed prediction endpoints with input contracts

~56 min to study properly

A serialized model sitting on disk cannot diagnose patients. We wrap it in a REST API so web clients, hospital electronic health record (EHR) systems, and mobile apps can request predictions over HTTP.

Checkpoint Β· the examiner asks

How does Pydantic schema validation enforce data contracts at the API boundary, and why is this critical for maintaining 99.9% uptime in production healthcare ML?

In one minute

Wrap the model in a FastAPI service so other systems can call it over HTTP. Pydantic validates every field at the boundary (age 0–120, positive glucose, enum categories) and returns HTTP 422 instead of crashing the model. Use async for I/O-bound work and multiple Uvicorn workers or threadpools for CPU-bound inference; Little's Law L = λ·W links throughput, latency and concurrency.

Traps examiners look for
  • Doing CPU-heavy inference directly in an async handler and blocking the event loop.
  • Validating inputs inside model code instead of at the API boundary.
  • Forgetting to convert milliseconds to seconds in Little's Law.
Read

Study notes

01Why an API

A model file on disk can't help a nurse at triage. Wrapping it in a REST API lets any system β€” the hospital's electronic health record, a mobile app, a dashboard β€” send patient data over HTTP and get a prediction back as JSON.

FastAPI is the de-facto choice for Python ML serving: it's built on Starlette (async web) and Pydantic (validation), generates interactive OpenAPI docs at /docs automatically, and is fast enough that the model, not the framework, is the bottleneck.

02Pydantic contracts and HTTP 422

class Patient(BaseModel):
    age: float = Field(ge=0, le=120)
    avg_glucose_level: float = Field(gt=0, le=400)
    bmi: float | None = Field(default=None, ge=10, le=80)
    hypertension: bool
    smoking_status: Literal["never", "formerly", "smokes", "unknown"]

@app.post("/predict")
def predict(p: Patient) -> Prediction: ...

Every request is checked at the boundary: wrong types, missing required fields, out-of-range values and unknown categories are rejected with HTTP 422 Unprocessable Entity and a JSON body naming the exact field and problem. Your model code never sees invalid input, so it can't crash or β€” worse β€” return a prediction for an impossible patient.

Validation is also documentation: the same schema appears in /docs for the integrators.

03Async, workers and CPU-bound inference

async def endpoints let one process handle many requests that are waiting β€” on a database, a log write, another HTTP call. But model inference is CPU-bound: while XGBoost computes, the event loop is blocked and nothing else is served.

Options:

  • Declare the endpoint with plain def: FastAPI runs it in a threadpool.
  • Run several Uvicorn workers (processes): uvicorn app.main:app --workers 4 or Gunicorn with Uvicorn workers. Each worker loads its own model copy.
  • Offload heavy jobs to a queue (Celery/RQ) and return a job id.

Load the model once per worker at startup, never per request.

04Little's Law and latency under load

Little's Law: L = Ξ» Γ— W β€” average requests in the system = arrival rate Γ— average time each spends. At 120 req/s and 25 ms latency, 3 requests are in flight on average.

Capacity: with c workers and service time S per request, the service can complete c / S requests per second (4 workers Γ— 1000/20 ms = 200 req/s). Utilisation ρ = Ξ» / capacity.

Latency doesn't grow linearly with load. A simple queueing approximation is W β‰ˆ S / (1 βˆ’ ρ): at ρ = 50% latency doubles; at 90% it's 10Γ—; at ρ β‰₯ 1 the queue grows without bound. That's why services are sized to run at roughly 60–70% utilisation, and why p99 latency (the slowest 1%) is monitored, not just the mean.

Watch it done

Worked examples

Worked example 1

Little's Law

The stroke API handles 80 requests/s with an average latency of 45 ms. How many requests are in flight, on average?

    Worked example 2

    How many workers?

    Inference takes 20 ms. Peak traffic is 300 req/s and you want utilisation ≀ 70%. How many workers do you need?

      Vocabulary

      Key terms

      FastAPI + Pydantic v2 Performance

      Pydantic v2 was completely rewritten in Rust, delivering 5Γ— to 50Γ— faster JSON parsing and validation. This allows a single FastAPI worker to validate thousands of patient payloads per second with sub-millisecond CPU overhead.

      Practise

      Quick check

      0 of 8 correct so far. Wrong answers come with an explanation β€” and the buddy can unpack them.

      Q1A hospital client sends {'age': -5, 'bmi': 'obese'} to a FastAPI endpoint requiring float bmi and age > 0. What happens?

      Q2What is Little's Law relating Concurrency (L), Throughput (Ξ»), and Latency (W)?

      Q3An invalid field in a FastAPI request body returns…

      Q4Why can a CPU-heavy predict() inside async def hurt throughput?

      Q5Ξ» = 200 req/s, W = 15 ms. L = ?

      Q6What does Literal['never','formerly','smokes'] enforce?

      Q7Utilisation rises from 50% to 90%. Roughly what happens to queueing latency?

      Q8Where should the model be loaded in a multi-worker deployment?

      Exam-style

      Written practice

      Short answer5 marks

      How does Pydantic protect the model code in a FastAPI service? Give two concrete validation rules for the stroke API.

      Short answer5 marks

      State Little's Law and use it to explain why latency rises sharply as a service approaches capacity.

      Long answer12 marks

      Design the /predict service for the stroke model so it is correct, fast and safe under hospital load. Cover the schema, model loading, concurrency model, error handling, capacity planning for 250 req/s at 20 ms inference, and what you would monitor.

      Watch

      Videos

      Fresh picks are chosen by AI every day.
      Hands-on

      FastAPI Model Serving, Pydantic Schema Validation & Latency Simulator

      5 guided tasks: predict, experiment, explain β€” then challenge yourself. Plus 2 coding exercises you run right in the browser.

      Start the lab