$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогПроизводительность FastAPI: от медленных сериализаций до правильных типов ответов

Производительность FastAPI: от медленных сериализаций до правильных типов ответов

$ sudo teach IT
·23 июля 2026 г.·10 мин·54
Производительность FastAPI: от медленных сериализаций до правильных типов ответов

Разберём, где теряется время в веб-приложении: Pydantic, JSON-обвязка, N+1, пул соединений и настройки сервера.

Содержание
1) Где реально тратится время в FastAPI1.1 Разделим обработку запроса на этапы1.2 Быстрый способ диагностики: измеряйте по стадиям2) Сериализация Pydantic: почему «модели» могут быть медленными2.1 Pydantic строит и валидирует, даже когда это не нужно2.2 Слишком большие ответы и лишние поля2.3 responsemodel и «двойная работа»2.4 Нюанс Pydantic v2: сериализация может быть быстрее, но есть ловушки3) JSON-обвязка: почему «просто JSON» тоже стоит денег3.1 Кодировщик JSON и размер документа3.2 Streaming и когда он действительно нужен4) N+1 и данные: самая частая причина «медленного FastAPI»4.1 Что такое N+1 в API-контексте4.2 Как диагностировать N+14.3 Как исправлять: eager loading, join, батчи5) Пул соединений и работа с БД/HTTP-клиентами5.1 Пул — это не просто «чтобы быстрее»5.2 Признаки проблем с пулом5.3 Async-драйверы: важно, что вы используете5.4 Таймауты и ретраи: осторожно6) Правильные типы ответов в FastAPI: меньше преобразований — меньше времени6.1 Зачем вообще говорить о типах ответов6.2 Когда выгодно возвращать Response, а не модель6.3 Custom JSON: где оптимизация может быть реальной7) Настройки сервера: Uvicorn/Gunicorn, workers, concurrency7.1 Почему серверные настройки влияют на «FastAPI скорость»7.2 Uvicorn: основные параметры, которые стоит проверить7.3 Gunicorn + Uvicorn workers8) Системный чек-лист производительности: от гипотез к исправлениям8.1 Сначала — измерьте и выделите доминирующую стадию8.2 Типовые «быстрые победы»8.3 Неочевидные проблемы, которые встречаются регулярно9) Практический пример: как поменять модель ответа и сократить время9.1 Было: большая схема и вложенные модели

FastAPI часто воспринимают как «быстрый фреймворк», и в целом это справедливо: он построен на Starlette и ASGI, а значительная часть пути обработки запроса — на стороне Python-кода и механизмов сериализации. Но в реальных сервисах скорость легко деградирует по причинам, которые не имеют отношения к «выбранному фреймворку». На практике узкие места почти всегда находятся в одном из слоёв:

  • сериализация и валидация на Pydantic;
  • JSON-обвязка и форматирование ответов;
  • проблемы модели доступа к данным (N+1 запросов);
  • работа с БД: пул соединений, настройки транзакций, тип драйвера;
  • серверная часть: настройки Uvicorn/Gunicorn/worker’ов, ограничение конкуренции, keep-alive.

В этой статье разберём, как системно найти причину замедления и что делать, чтобы FastAPI-сервис перестал терять время на «лишнюю работу». Будем говорить не общими фразами, а о конкретных механизмах: что именно делает Pydantic, почему иногда «хороший» код становится медленным, как правильно выбирать типы ответов и как настроить инфраструктуру.


1) Где реально тратится время в FastAPI

1.1 Разделим обработку запроса на этапы

Чтобы не гадать, полезно мысленно разложить обработку запроса:

  1. ASGI слой принимает запрос.
  2. Зависимости FastAPI выполняют подготовку данных (dependency injection).
  3. Request body парсится (если есть).
  4. Валидация входных данных и построение моделей Pydantic.
  5. Ваш обработчик выполняет бизнес-логику: запросы к БД/внешним сервисам.
  6. Результат оборачивается в модель ответа и сериализуется в JSON.
  7. Тело ответа кодируется и отправляется клиенту.

Когда сервис медленный «в целом», обычно виноваты этапы 5–6 (данные и сериализация). Но бывает и серверная часть: неправильное число worker’ов, слишком маленький пул, отсутствие лимитов на конкурентность.

1.2 Быстрый способ диагностики: измеряйте по стадиям

В Python без профилировщика легко «улететь» в предположения. Рекомендуемый минимум:

  • логировать длительность эндпоинта целиком;
  • отдельно замерять время запроса к БД и время сериализации ответа (если есть возможность);
  • использовать системный профайлер для подтверждения (например, py-spy или yappi).

Для практического ориентира: если большая часть времени уходит на CPU и JSON/валидацию — оптимизировать надо именно их. Если время уходит на ожидание I/O (БД/HTTP) — ищите N+1, пул, таймауты, индексацию и конкуренцию.


2) Сериализация Pydantic: почему «модели» могут быть медленными

2.1 Pydantic строит и валидирует, даже когда это не нужно

У Pydantic есть две связанные, но разные задачи:

  • валидация: проверка входных данных (и построение модели);
  • сериализация: преобразование модели в словарь/JSON.

На выходе FastAPI обычно сериализует объект ответа. Если вы возвращаете Pydantic-модель, она уже построена, но сериализация и проход по структуре всё равно требует времени. Если же вы возвращаете «сырой» dict со сложными вложенными объектами, FastAPI может начать преобразования и/или валидацию по схеме ответа (в зависимости от того, как вы объявили response_model).

Типовая ошибка: объявить response_model=HugeModel и возвращать большой граф объектов (списки вложенных моделей, дополнительные вычисления). Даже если бизнес-логика быстрая, сериализация становится доминирующей.

2.2 Слишком большие ответы и лишние поля

Чем больше данных, тем выше цена:

  • больше обходов по структуре;
  • больше памяти и аллокаций;
  • больше работы JSON кодировщика.

Проверьте:

  • реальные поля, которые клиенту нужны;
  • возможность выделить «лёгкий» эндпоинт с меньшим набором полей;
  • использование exclude / include на уровне сериализации (или на уровне модели через model_config).

Например, в Pydantic v2 можно уменьшить данные так:

code
from pydantic import BaseModel, ConfigDict

class UserOut(BaseModel):
    id: int
    email: str
    # тяжёлые/неиспользуемые поля можно исключить по умолчанию

    model_config = ConfigDict(
        ser_json_exclude_none=True,
    )

А дальше — аккуратно выбирать, что именно сериализовать.

2.3 response_model и «двойная работа»

Очень распространённый сценарий:

  • вы вручную строите Pydantic-объект в эндпоинте;
  • одновременно объявляете response_model, и FastAPI снова пытается привести/проверить ответ.

В идеале ответ должен либо уже соответствовать модели, либо быть «сырой» структурой, которую FastAPI быстро обернёт один раз. На практике легко получить двойной проход.

Совет: если вы уже возвращаете корректный объект Pydantic и не хотите повторной проверки, добивайтесь согласованности схем. В ряде случаев — возвращайте модель и убирайте response_model (или используйте явный тип возврата), но это зависит от вашего стиля и требований к документации.

2.4 Нюанс Pydantic v2: сериализация может быть быстрее, но есть ловушки

Pydantic v2 улучшил производительность, но «быстрее» ≠ «бесплатно». Ловушки обычно такие:

  • вложенные модели, каждая из которых требует сериализации;
  • поля с пользовательскими сериализаторами (field_serializer / model_serializer) — иногда они удобны, но часто добавляют Python-код в горячий путь;
  • принудительное преобразование типов (например, Decimal и даты) в каждый ответ.

Если вы видите в профиле долю времени именно в сериализаторе — оптимизируйте схемы (и, по возможности, переносите преобразования ближе к источнику данных).


3) JSON-обвязка: почему «просто JSON» тоже стоит денег

3.1 Кодировщик JSON и размер документа

Даже если сериализация моделей оптимизирована, остаётся этап:

  • кодирование в JSON строку;
  • отправка по сети.

У JSON есть минимум работы по:

  • экранированию;
  • преобразованию типов (особенно нестандартных);
  • формированию строк.

Если вы возвращаете большие массивы или поля с большой вложенностью, скорость начнёт упираться в CPU и в аллокации.

Практический подход:

  • уменьшайте объём ответа (см. предыдущий раздел);
  • рассматривайте пагинацию;
  • избегайте сериализации «полного объекта» для списка, где нужен только summary.

3.2 Streaming и когда он действительно нужен

Если ответ большой (например, выгрузка), «держать всё в памяти» не всегда разумно. Тогда лучше использовать streaming: StreamingResponse или отправку чанками.

Пример для сценария «объёмный JSON» (упрощённо):

code
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
from typing import Iterable

app = FastAPI()

def iter_users() -> Iterable[dict]:
    # генератор данных (идеально — из БД постранично)
    for i in range(10_000):
        yield {"id": i, "name": f"user{i}"}

@app.get("/users/stream")
def users_stream():
    def gen():
        yield "["
        first = True
        for item in iter_users():
            if not first:
                yield ","
            else:
                first = False
            yield json.dumps(item, ensure_ascii=False)
        yield "]"

    return StreamingResponse(gen(), media_type="application/json; charset=utf-8")

Streaming — не «ускоритель любой ценой», но он:

  • снижает пиковое потребление памяти;
  • иногда помогает начать отдавать клиенту раньше.

Но важно: сериализация всё равно занимает время; просто вы не заставляете сервис сначала построить огромную строку.


4) N+1 и данные: самая частая причина «медленного FastAPI»

4.1 Что такое N+1 в API-контексте

N+1 — это паттерн, когда:

  • вы выполняете 1 запрос, чтобы получить список сущностей (N),
  • а затем делаете N дополнительных запросов, чтобы получить связанные данные.

Для веба это почти идеальный рецепт медленного ответа: даже при быстрых запросах к БД количество round-trip’ов растёт линейно.

Пример: список пользователей и их адреса. Если код выглядит как «получить пользователей — для каждого запросить адрес», то получите N+1.

4.2 Как диагностировать N+1

Диагностировать можно несколькими путями:

  • включить логирование запросов к БД и посмотреть количество запросов на один HTTP-запрос;
  • использовать профилировщик, который покажет много одинаковых вызовов репозитория;
  • проверить профилирование на уровне SQL: если на запрос уходит сотни запросов — это оно.

4.3 Как исправлять: eager loading, join, батчи

В зависимости от ORM (SQLAlchemy/asyncpg, Tortoise, Django ORM и т.д.) методы отличаются, но идеи одинаковы:

  • eager loading: загрузить связи заранее одним (или небольшим числом) запросов;
  • join: получать данные плоско и затем маппить;
  • батчинг: вместо N отдельных запросов делать запросы вида WHERE id IN (...).

В SQLAlchemy (пример концептуальный, без привязки к конкретной схеме):

code
from sqlalchemy import select
from sqlalchemy.orm import joinedload

# предположим: User -> addresses (один ко многим)
stmt = (
    select(User)
    .options(joinedload(User.addresses))
)

Смысл: заменить N запросов на 1 (или несколько контролируемых).


5) Пул соединений и работа с БД/HTTP-клиентами

5.1 Пул — это не просто «чтобы быстрее»

Пул соединений регулирует:

  • количество одновременных соединений к БД;
  • ожидание при нехватке соединений;
  • нагрузку на БД (и её возможность обслуживать конкурентные запросы).

Если пул слишком мал, запросы к БД будут стоять в очереди — API будет медленным, даже если SQL быстрый.

Если пул слишком велик, вы рискуете:

  • перегрузить БД;
  • получить рост латентности из-за контеншна;
  • упереться в лимиты сервера или сети.

5.2 Признаки проблем с пулом

Типовые симптомы:

  • в профиле много времени уходит на await в драйвере БД, но SQL сам по себе быстрый;
  • растёт время ответа при увеличении нагрузки (а не только средняя задержка);
  • сервер показывает «пилу» по времени: запросы иногда быстрые, иногда очередь.

5.3 Async-драйверы: важно, что вы используете

В FastAPI с async-эндпоинтами вы обычно используете async-драйверы (asyncpg, SQLAlchemy async, etc.). Если где-то проскакивает синхронная БД/блокирующие вызовы — вы «блокируете» event loop и получаете каскад задержек.

Поэтому:

  • следите, чтобы I/O были async;
  • не выполняйте тяжёлую работу в event loop (расчёты выносите в воркеры/фоновые потоки).

5.4 Таймауты и ретраи: осторожно

Слишком агрессивные таймауты приводят к лишним ошибкам и ретраям (которые сами по себе создают нагрузку). Слишком большие — замораживают ресурсы и увеличивают среднюю задержку.

Для производительности лучше:

  • нормализовать таймауты на уровне клиента и сервера;
  • предусмотреть circuit breaker или хотя бы ограничение ретраев.

6) Правильные типы ответов в FastAPI: меньше преобразований — меньше времени

6.1 Зачем вообще говорить о типах ответов

Потому что в FastAPI типы напрямую влияют на то, как фреймворк сериализует данные, как валидирует их под response_model и какие преобразования выполняет.

Например:

  • dict и простые типы: сериализация предсказуемая.
  • Pydantic-модели: сериализация обычно тоже предсказуемая, но зависит от размера и настроек.
  • Response / JSONResponse с уже готовым JSON: меньше работы, если вы действительно контролируете сериализацию.
  • bytes / StreamingResponse: другой путь обработки.

6.2 Когда выгодно возвращать Response, а не модель

Если вы по какой-то причине уже подготовили JSON (например, кешированный ответ в виде строки), можно вернуть готовое тело через Response или JSONResponse.

Пример: кеш на стороне приложения хранит готовый JSON:

code
from fastapi import FastAPI, Response
import orjson

app = FastAPI()

@app.get("/cached")
async def cached():
    # допустим, этот JSON вы получили из кеша как строку/байты
    data = {"ok": True, "items": list(range(100))}
    body = orjson.dumps(data)  # bytes

    return Response(content=body, media_type="application/json")

Это убирает лишний проход «модель -> dict -> json string», оставляя только тот вариант, который вы контролируете.

Важно: это имеет смысл только если у вас действительно есть основание не использовать обычный механизм сериализации. Иначе вы просто дублируете работу.

6.3 Custom JSON: где оптимизация может быть реальной

FastAPI/Starlette используют стандартный json.dumps через Starlette JSONResponse. Для части систем можно переключить сериализацию на более быстрые библиотеки (например, orjson). Но в этом месте важно не создать несовместимость по форматам.

Один из подходов — глобально настроить класс ответа или возвращать готовые bytes (см. предыдущий пример). Это даёт максимальный контроль и минимизирует сюрпризы.


7) Настройки сервера: Uvicorn/Gunicorn, workers, concurrency

7.1 Почему серверные настройки влияют на «FastAPI скорость»

Даже идеальная оптимизация кода может не дать эффекта, если:

  • выбран неправильный режим запуска;
  • слишком мало workers;
  • неправильно выставлена очередь соединений;
  • event loop перегружен CPU-bound задачами.

FastAPI как ASGI-приложение обслуживает множество конкурентных запросов в рамках event loop, но CPU-bound участки всё равно будут тормозить.

7.2 Uvicorn: основные параметры, которые стоит проверить

Обычно смотрят на:

  • --workers: количество процессов (для CPU-bound частично помогает);
  • --limit-concurrency: ограничение числа одновременных обработок (полезно для защиты от перегрузок);
  • --backlog: очередь входящих соединений;
  • keep-alive и таймауты HTTP.

Если вы видите, что при росте нагрузки latency растёт лавинообразно — вероятно, вы упираетесь не в код сериализации, а в насыщение ресурсов.

7.3 Gunicorn + Uvicorn workers

При запуске в production часто используют Gunicorn с Uvicorn workers. Тогда важно корректно подобрать:

  • число worker’ов (обычно зависит от CPU и профиля нагрузки);
  • настройки timeout’ов;
  • модель логики (preload, reload — аккуратно).

Ключевой принцип: подбирайте worker’ы под реальные узкие места. Если у вас чистый I/O, иногда лучше больше конкуренции в одном процессе. Если же много сериализации/валидации/CPU-работы — больше процессов может помочь.


8) Системный чек-лист производительности: от гипотез к исправлениям

8.1 Сначала — измерьте и выделите доминирующую стадию

Без профилирования вы рискуете оптимизировать не то. Сделайте порядок:

  1. Снять метрики: p50/p95/p99 latency, error rate, throughput.
  2. На уровне APM/логов понять распределение времени: app time vs DB time.
  3. При необходимости — локальный профайлинг эндпоинта под нагрузкой.

8.2 Типовые «быстрые победы»

  • Уменьшить размер ответа: пагинация, exclude полей, отдельные схемы для списка.
  • Убрать N+1: eager loading/join/batch.
  • Проверить пул: не хватает соединений или, наоборот, перегружаем БД.
  • Убедиться, что нет синхронного блокирующего I/O внутри async.
  • Рассмотреть streaming для очень больших ответов.

8.3 Неочевидные проблемы, которые встречаются регулярно

  • Пользовательские сериализаторы на горячем пути (медленные форматирования).
  • Двойная валидация/преобразования из-за несовпадения response_model и возвращаемых объектов.
  • Отсутствие лимитов на конкуренцию: сервис начинает «сам себя душить».
  • Недостаточное количество worker’ов/неудачная конфигурация очередей.
  • Отсутствие таймаутов: один зависший внешний сервис «забивает» ресурсы.

9) Практический пример: как поменять модель ответа и сократить время

Рассмотрим условный кейс: эндпоинт возвращает список сущностей, а схема ответа включает поля, которые вычисляются на лету или содержат вложенные модели.

9.1 Было: большая схема и вложенные модели

code
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

class ItemOut(BaseModel):
    id: int

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

API на FastAPI

Курс по теме

API на FastAPI

Погрузитесь в мир самого быстрого фреймворка в Python – FastAPI! Курс для тех, кто хочет освоить полноценную профессию, и писать эндпоинты на enterprise-уровне!

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
980
FastAPI и фоновые задачи: очередь vs BackgroundTasks, idempotency и обработка ретраев
fastapi

FastAPI и фоновые задачи: очередь vs BackgroundTasks, idempotency и обработка ретраев

На примерах объясним, когда достаточно BackgroundTasks, а когда нужна отдельная очередь/воркер. Разберём, как обеспечить воспроизводимость выполнения, безопасные ретраи и корректные статусы задач в API.

2 августа 2026 г.
720
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

1) Где реально тратится время в FastAPI1.1 Разделим обработку запроса на этапы1.2 Быстрый способ диагностики: измеряйте по стадиям2) Сериализация Pydantic: почему «модели» могут быть медленными2.1 Pydantic строит и валидирует, даже когда это не нужно2.2 Слишком большие ответы и лишние поля2.3 responsemodel и «двойная работа»2.4 Нюанс Pydantic v2: сериализация может быть быстрее, но есть ловушки3) JSON-обвязка: почему «просто JSON» тоже стоит денег3.1 Кодировщик JSON и размер документа3.2 Streaming и когда он действительно нужен4) N+1 и данные: самая частая причина «медленного FastAPI»4.1 Что такое N+1 в API-контексте4.2 Как диагностировать N+14.3 Как исправлять: eager loading, join, батчи5) Пул соединений и работа с БД/HTTP-клиентами5.1 Пул — это не просто «чтобы быстрее»5.2 Признаки проблем с пулом5.3 Async-драйверы: важно, что вы используете5.4 Таймауты и ретраи: осторожно6) Правильные типы ответов в FastAPI: меньше преобразований — меньше времени6.1 Зачем вообще говорить о типах ответов6.2 Когда выгодно возвращать Response, а не модель6.3 Custom JSON: где оптимизация может быть реальной7) Настройки сервера: Uvicorn/Gunicorn, workers, concurrency7.1 Почему серверные настройки влияют на «FastAPI скорость»7.2 Uvicorn: основные параметры, которые стоит проверить7.3 Gunicorn + Uvicorn workers8) Системный чек-лист производительности: от гипотез к исправлениям8.1 Сначала — измерьте и выделите доминирующую стадию8.2 Типовые «быстрые победы»8.3 Неочевидные проблемы, которые встречаются регулярно9) Практический пример: как поменять модель ответа и сократить время9.1 Было: большая схема и вложенные модели