---
title: Is your Python web framework really the performance bottleneck?
description: Using Logfire to investigate where time is really spent during a web request
date: '2026-02-17'
authors:
  - Victorien Plot
categories:
  - Pydantic Logfire
  - Performance
canonical: 'https://pydantic.dev/articles/web-framework-performance'
---

> Markdown version of [Is your Python web framework really the performance bottleneck?](https://pydantic.dev/articles/web-framework-performance) — the canonical HTML page.
>
> By [Victorien Plot](https://pydantic.dev/authors/victorien-plot.md) · 2026-02-17 · Pydantic Logfire, Performance
>
> Related: [You perfected the wrong agent](https://pydantic.dev/articles/agents-week.md) · [Observability tools agents want](https://pydantic.dev/articles/observability-tools-agents-want.md)
>
> All articles: [/articles.md](https://pydantic.dev/articles.md) · Site index: [/llms.txt](https://pydantic.dev/llms.txt)

---

It is pretty common to see benchmarks of new Python web frameworks popping up from time to time, often without actually representing real use cases.
Within a web request to one of your backend endpoints, many things can happen (on top of the framework's own logic and data validation): cache and/or database queries,
requests to external services, processing of data, etc., and I always wondered if the speed of the framework really matters in practice.

In this blog post, we are going to use [our observability platform, Pydantic Logfire](https://pydantic.dev/logfire) to investigate what usually takes longest in a web request.


:::note
This experiment is by no means a substitute for a proper benchmark measured on a production-deployed application where the environment is representative of real-world conditions.
:::


## Table of Contents

* [Setting Up the Experiment](#setting-up-the-experiment)
* [Profiling a Heavy Payload](#profiling-a-heavy-payload)
* [Profiling a Light Payload](#profiling-a-light-payload)
* [Where Does FastAPI Actually Spend Time?](#where-does-fastapi-actually-spend-time)
* [Conclusion](#conclusion)

<a id="setting-up-the-experiment"></a>
## Setting Up the Experiment

To achieve this, we are going to use [FastAPI](https://fastapi.tiangolo.com/), and define a dummy POST endpoint that will validate data from the request's body,
perform a SQL query to a local SQLite database, and return the body unchanged. We're going to [instrument our app](https://pydantic.dev/docs/logfire/integrations/web-frameworks/fastapi/)
using Logfire to get a sense of where time is being spent:

```python
from pydantic import BaseModel
from fastapi import FastAPI, Body

import logfire

app = FastAPI()

logfire.configure(token='pylf_v1_...')

logfire.instrument_fastapi(app)
logfire.instrument_sqlite3()
# As FastAPI uses Pydantic to validate the body:
logfire.instrument_pydantic()


# Using LLMs is really useful to generate dummy nested models like this:
class Data(BaseModel):
    customer: Customer
    items: list[OrderItem]
    payments: list[Payment]
    notes: list[str]
    priority: int = Field(ge=1, le=10)
    scheduled_time: time | None
    # More nested fields
    ...


@app.post('/test', response_model=Data)
async def test(body: Annotated[Data, Body()]):
    query_results = run_sqlite_heavy_query()
    return body
```

Not shown in the code snippet, but `run_sqlite_heavy_query()` is defined as making a SQL query using [`sqlite3`](https://docs.python.org/3/library/sqlite3.html) file database
(filled with a few thousand rows), and making a query involving multiple joins [^1].

<a id="profiling-a-heavy-payload"></a>
## Profiling a Heavy Payload

We then use our favourite LLM to generate a cURL command with sample data to be sent (~100 JSON objects, structured in a way that matches our `Data` Pydantic model), and see
what happens on Logfire:

![Logfire trace showing FastAPI request breakdown with Pydantic validation and SQL query spans for a heavy payload](https://pydantic.dev/assets/blog/victorien/first-span.png)

As FastAPI is instrumented by Logfire, a root [span](https://pydantic.dev/docs/logfire/get-started/concepts/#what-is-a-span) is created and wraps the whole execution of the request. This allows
us to get a clear breakdown of what is happening:

- The first span is the validation of the JSON payload to produce a `Data` instance.
- The second span is the SQL query being run.
- The third span is the validation of the returned data. It is way faster than the input validation, as Pydantic does _not_ re-validate instances by default [^2].

You will notice the small gaps between each span, which presumably is time being spent in the actual FastAPI logic (routing, etc). We can see from the experiment
that with a relatively large JSON payload and expensive query, a really small amount of time is being used by the actual web framework.

<a id="profiling-a-light-payload"></a>
## Profiling a Light Payload

Now when doing performance measurements, relativity is what matters: FastAPI's own logic is insignificant compared to the data validation/DB query in our example, but might not be
if we reduce the size/complexity of the payload/query. To test this, we define a `/test-light` endpoint, reducing the payload and query complexity:

![Logfire trace showing lighter payload with more visible FastAPI framework overhead between spans](https://pydantic.dev/assets/blog/victorien/light-span.png)

Now it seems like the gaps between the spans are more noticeable. I got curious, and wanted to see where that time was being spent: I took a dive into the `fastapi`
logic, and wrapped some parts of the routing logic with Logfire spans, and added some logs as well. Here is the result:


<iframe
  title="Fully instrumented Logfire trace of a FastAPI request showing routing, validation, and query spans"
  width="100%"
  height="600"
  src="https://logfire-eu.pydantic.dev/public-trace/d4c8040e-d7dc-40a8-9921-1ff8d0483d7c?spanId=0532c95d0ab852fb&embedded=true&theme=light">
</iframe>

**[👉 View the Live Logfire Trace](https://logfire-eu.pydantic.dev/public-trace/d4c8040e-d7dc-40a8-9921-1ff8d0483d7c?spanId=0532c95d0ab852fb)**

<a id="where-does-fastapi-actually-spend-time"></a>
## Where Does FastAPI Actually Spend Time?

Note that to try matching a real FastAPI app, I artificially registered a thousand dummy routes, so that the route matching logic would actually be realistic.
What I noticed is that most of the time is spent in things "outside of FastAPI's control". The request's body is first being parsed as bytes, then as JSON [^3],
the actual run of the endpoint function is almost all about running the SQL query, and a bit of time is spent serializing the response body.

Some non-negligible time is spent in the route matching, so it may be worth looking into it, and see if it can be optimized.

More importantly, we can easily deduce that the framework on its own is rarely going to be the bottleneck. Also note that this was all run locally, and so does
not account for any latency between the client and the server.

:::commend{title="Can Logfire be used to investigate performance issues?"}
If you ever notice high duration spans in your production app, trying to apply what we did here might be useful. Try wrapping your code in spans (using the
[`logfire.span()`](https://pydantic.dev/docs/logfire/api/logfire/#logfire.Logfire.span) context manager), in the most fine-grained way possible.

Using the [explore view](https://pydantic.dev/docs/logfire/observe/explore/), you can also aggregate the duration of the spans you suspect are slow to get
a better sense of how slow it actually is (and not taking a couple samples, that could have been slow for other reasons).
:::

<a id="conclusion"></a>
## Conclusion

Our experiment with [Pydantic Logfire](https://pydantic.dev/logfire) shows that Python web framework overhead is typically negligible compared to real application work like data validation, database queries, and response serialization. Even with a lighter payload and simpler query, FastAPI's own routing and request handling accounted for only a small fraction of the total request duration.

Before reaching for a "faster" framework, consider profiling your actual application with an observability tool like Logfire. The bottleneck is likely in your application logic, not the framework, and [distributed tracing](https://pydantic.dev/articles/javascript-observability) can help you pinpoint exactly where.

[Source code for this experiment](https://gist.github.com/Viicos/2339e207aeec25b608942bb28eb9c299).

[^1]: As we mentioned, this isn't representative of a real deployment as this does not account for any latency to access the database, but should still be relatively accurate.
[^2]: You will want to use the [`revalidate_instances`](https://pydantic.dev/docs/validation/latest/api/pydantic/config/#pydantic.config.ConfigDict.revalidate_instances) configuration value to do so.
[^3]: FastAPI currently relies on [Starlette](https://starlette.dev/), which in turn unconditionally uses the standard library [`json`](https://docs.python.org/3/library/json.html)
      module to parse the body. One interesting experiment would be to use Pydantic's native [JSON validation](https://pydantic.dev/docs/validation/latest/concepts/json/) instead, directly
      as part of the validation step.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "BlogPosting",
      "mainEntityOfPage": {
        "@type": "WebPage",
        "@id": "https://pydantic.dev/articles/web-framework-performance"
      },
      "headline": "Is Your Python Web Framework Really the Performance Bottleneck? | Pydantic Logfire",
      "description": "Discover where time is actually spent during Python web requests. We use Pydantic Logfire to profile FastAPI performance, comparing framework overhead vs data validation and database queries.",
      "keywords": "Python web framework performance, FastAPI benchmarks, Pydantic Logfire, web request profiling, Python performance optimization, OpenTelemetry tracing, FastAPI observability, Python API performance",
      "image": {
        "@type": "ImageObject",
        "url": "",
        "width": "",
        "height": ""
      },
      "author": {
        "@type": "Person",
        "name": "Victorien Plot",
        "url": "https://github.com/Viicos"
      },
      "about": [
        {
          "@type": "Thing",
          "name": "Web Framework Performance",
          "sameAs": "https://en.wikipedia.org/wiki/Web_framework"
        },
        {
          "@type": "Thing",
          "name": "Python",
          "sameAs": "https://en.wikipedia.org/wiki/Python_(programming_language)"
        },
        {
          "@type": "Thing",
          "name": "FastAPI",
          "sameAs": "https://en.wikipedia.org/wiki/FastAPI"
        }
      ],
      "publisher": {
        "@type": "Organization",
        "name": "Pydantic",
        "url": "https://pydantic.dev/",
        "logo": {
          "@type": "ImageObject",
          "url": "data:image/png;base64,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",
          "width": "200",
          "height": "200"
        }
      },
      "datePublished": "2026-02-17"
    }
  ]
}
</script>
