Все уроки
БэкендСредний15 мин чтенияПолезно знать

Python, Django и FastAPI для веб-разработчиков

Python, который нужен для веба, «батарейки» Django и когда лучше подходит FastAPI.

  • Python
  • Django
  • FastAPI
  • ORM
  • Async
Шаг 1/5
Приходит запрос: POST /orders. Router смотрит на URL и метод и выбирает action контроллера, который его обработает.

За 30 секунд

Python — читаемый язык общего назначения, на котором работает большая доля веб-бэкендов, задач с данными и AI. Django — его веб-фреймворк по принципу «batteries included»: ORM, миграции, админка, auth, формы и безопасные настройки по умолчанию в одном пакете. FastAPI — более лёгкий async-first фреймворк для типизированных JSON API. Если вы знаете PHP, Node.js или другой бэкенд-стек, этот урок наложит то, что вы уже знаете, на мир Python.

Не в моём ежедневном стеке — я понимаю принципы и изучу детали, когда это понадобится проекту.

Почему это важно

  • Python — язык по умолчанию для data science и AI, поэтому многие команды хотят, чтобы веб-бэкенд был на том же языке, что и их модели и скрипты.
  • Django из коробки даёт админку, auth и ORM продакшен-уровня, что позволяет небольшой команде очень быстро построить продукт с большим количеством данных или back office.
  • FastAPI стал популярным выбором для типизированных async JSON API и для отдачи моделей машинного обучения через HTTP.
  • Для разработчика, пришедшего с Laravel или NestJS, Django и FastAPI очень близки к знакомым идеям, так что проект на Python — это короткое обучение, а не старт с нуля.

Как это работает

01Python на одной странице для веб-разработчиков

Python использует отступы вместо фигурных скобок, не требует точек с запятой и читается почти как английский. Переменные типизированы динамически, но современный код добавляет type hints (name: str, -> list[int]), которые инструменты вроде mypy или Pyright проверяют до запуска. Основные типы, которыми вы будете пользоваться каждый день: str, int, float, bool, None, list, dict, tuple и set. Функции объявляются через def, классы — через class, а модули — это просто .py-файлы, которые вы импортируете. Python 3.14 — текущая стабильная ветка (3.15 ожидается в цикле октября 2026 года), и всегда работайте внутри виртуального окружения, чтобы у каждого проекта были свои зависимости. Быстрый современный способ управлять версиями Python, окружениями и пакетами — uv; pip с venv по-прежнему работает везде.

  • Truthy и falsy: пустой list, пустая строка, 0 и None — все falsy.
  • List и dict comprehensions заменяют большинство вызовов map и filter.
  • async def и await существуют, но большая часть Python-кода всё ещё синхронная.
  • PEP 8 — это гайд по стилю; Ruff форматирует и линтит код по нему за миллисекунды.
python
from dataclasses import dataclass
 
@dataclass
class Product:
    name: str
    price: float
    tags: list[str]
 
def cheap(items: list[Product], limit: float = 10.0) -> list[str]:
    return [p.name for p in items if p.price < limit]
 
items = [Product("Mug", 8.5, ["home"]), Product("Lamp", 40.0, [])]
print(cheap(items))  # ['Mug']

02Создание Django-проекта

Django организован в project (настройки, корневые URL, точки входа WSGI/ASGI) и apps (самостоятельные фичи, например orders или blog). У каждого app есть модели, view, URL, регистрация в админке и миграции. Django 6.1 — текущий feature-релиз, а 5.2 — ветка с долгосрочной поддержкой; следующий LTS, 6.2, запланирован на апрель 2027 года. Django 6.0 добавил встроенный API фоновых задач, template partials и поддержку Content Security Policy. Поток запроса простой: URL resolver находит путь, вызывает view, view через модели читает или пишет данные и возвращает ответ — часто отрендеренный шаблоном или сериализованный в JSON.

bash
uv init shop && cd shop
uv add django
uv run django-admin startproject config .
uv run python manage.py startapp catalog
# add "catalog" to INSTALLED_APPS in config/settings.py
uv run python manage.py migrate
uv run python manage.py createsuperuser
uv run python manage.py runserver

03Модели и ORM

Модель — это Python-класс, который соответствует таблице. Каждый атрибут — это поле с типом и опциями, и Django генерирует миграции из изменений этих классов. ORM даёт вам QuerySet: ленивые цепочечные построители запросов, которые обращаются к базе только тогда, когда вы итерируете их, делаете срез или вычисляете. Связи задаются через ForeignKey, OneToOneField и ManyToManyField. Для производительности важнее всего два метода: select_related объединяет одиночные связи в один запрос, а prefetch_related загружает множественные связи вторым запросом. Без них цикл, который обращается к product.category, делает по запросу на каждую строку — классическая проблема N+1.

python
from django.db import models
 
class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
 
class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products")
    created_at = models.DateTimeField(auto_now_add=True)
 
    class Meta:
        indexes = [models.Index(fields=["category", "-created_at"])]
 
# One query with a JOIN instead of N+1:
recent = Product.objects.select_related("category").filter(price__lt=50)[:20]

04Админка: back office в десять строк

Админка Django читает ваши модели и строит полноценный CRUD-интерфейс с поиском, фильтрами, пагинацией и проверкой прав. Зарегистрируйте модель, опишите, какие колонки показывать и по каким полям фильтровать, — и сотрудники сразу могут управлять данными. Это одна из главных причин, по которым команды выбирают Django для внутренних инструментов и контентных продуктов. Относитесь к админке как к инструменту для доверенных сотрудников, а не как к интерфейсу для клиентов: спрячьте её за неочевидным путём, требуйте сильные пароли или SSO и используйте list_select_related, чтобы большие таблицы не вызывали N+1-запросы.

python
from django.contrib import admin
from .models import Product
 
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ("name", "category", "price", "created_at")
    list_filter = ("category",)
    search_fields = ("name",)
    list_select_related = ("category",)
    ordering = ("-created_at",)

05JSON API с Django REST Framework

По умолчанию Django рендерит HTML. Для JSON API большинство команд добавляют Django REST Framework (DRF). Его строительные блоки — сериализаторы (превращают модели в JSON и валидируют ввод, как Form Request и API Resource в Laravel вместе), view или viewset (обрабатывают HTTP-методы) и роутеры (генерируют URL). DRF также даёт классы аутентификации, права доступа, throttling, пагинацию и browsable API для разработки. Django Ninja — более новая альтернатива, которая использует type hints и Pydantic в стиле FastAPI, сохраняя при этом ORM и админку Django.

python
from rest_framework import serializers, viewsets, permissions
from .models import Product
 
class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ["id", "name", "price", "category"]
 
class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.select_related("category").order_by("id")
    serializer_class = ProductSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
 
# urls.py
# router = routers.DefaultRouter(); router.register("products", ProductViewSet)

06FastAPI: асинхронная типизированная альтернатива

FastAPI — микрофреймворк, построенный на Starlette (ASGI-тулкит) и Pydantic (валидация). Вы описываете формы запросов и ответов как Pydantic-модели с type hints, а FastAPI автоматически валидирует ввод, сериализует вывод и генерирует OpenAPI-документацию по адресу /docs. Он async-first, поэтому хорошо справляется с большим количеством параллельных запросов, упирающихся в I/O, — из-за этого он популярен для отдачи ML-моделей и API-шлюзов. Компромисс: в нём нет встроенных ORM, админки, миграций или системы auth. Вы сами выбираете SQLAlchemy или SQLModel, Alembic для миграций и свой auth. Выбирайте Django, когда нужен полноценный «opinionated» продуктовый фреймворк; выбирайте FastAPI, когда нужен небольшой, быстрый, типизированный API-сервис.

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
 
app = FastAPI()
 
class ProductIn(BaseModel):
    name: str = Field(min_length=1, max_length=200)
    price: float = Field(gt=0)
 
@app.post("/products", status_code=201)
async def create_product(body: ProductIn) -> dict:
    if body.name.lower() == "test":
        raise HTTPException(status_code=422, detail="Reserved name")
    return {"id": 1, **body.model_dump()}
 
# run: uv add "fastapi[standard]" && uv run fastapi dev main.py

07Запуск веб-приложений на Python в продакшене

В разработке вы используете runserver или fastapi dev, но никогда — в продакшене. Для синхронного Django-приложения классическая схема — Gunicorn с несколькими worker-процессами за nginx. Для асинхронного кода (FastAPI, async view в Django, WebSocket через Channels) вы запускаете ASGI-сервер, например Uvicorn, часто под управлением Gunicorn или как несколько worker-ов Uvicorn. Статические файлы собираются через collectstatic и раздаются nginx, CDN или WhiteNoise. Настройки берутся из переменных окружения, DEBUG должен быть False, ALLOWED_HOSTS должен быть задан, а manage.py check --deploy показывает недостающие настройки безопасности. Фоновая работа уходит в Celery, RQ, Dramatiq или новый tasks API Django с worker-бэкендом.

bash
# Django (WSGI) with 4 workers
gunicorn config.wsgi:application --workers 4 --bind 127.0.0.1:8000
 
# FastAPI or Django ASGI
uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4
 
# Before each deploy
python manage.py check --deploy
python manage.py migrate --noinput
python manage.py collectstatic --noinput

Когда применять

  • Продукты с большим количеством данных, CMS-подобные сайты и внутренние инструменты, где админка Django экономит недели работы.
  • Команды, которые уже используют Python для data science, ML или автоматизации и хотят один язык на всём стеке.
  • Типизированные JSON API или эндпоинты ML-моделей, которым нужна высокая конкурентность и автоматическая OpenAPI-документация (FastAPI).
  • Проекты, которые ценят стабильные, хорошо документированные, консервативные фреймворки с релизами долгосрочной поддержки.

Когда не стоит

  • Real-time системы с высокой конкурентностью, где вся команда уже сильна в Node.js или Go и инструменты уже на месте.
  • Когда у команды глубокий опыт в Laravel или NestJS, а требования к Python нет; смена стека имеет реальную цену.
  • Тяжёлая для CPU обработка запросов на чистом Python без выноса работы в очереди или нативные библиотеки.

Альтернативы

  • Laravel (PHP)

    Очень похожая философия «batteries included»: Eloquent, миграции, очереди, auth и большая экосистема.

  • NestJS (Node.js)

    Структурированный бэкенд на TypeScript с модулями и DI; один язык с фронтендом на React или Next.js.

  • Flask

    Минималистичный микрофреймворк на Python; гибкий, но всё собираете сами.

  • Django Ninja / Litestar

    Типизированные API в стиле Pydantic; Ninja сохраняет ORM и админку Django, Litestar — альтернатива, похожая на FastAPI.

Лучшие практики

  1. Фиксируйте версию Python и зависимости для каждого проекта через uv (pyproject.toml плюс uv.lock) или pip-tools; никогда не ставьте пакеты в системный Python.
  2. Используйте select_related и prefetch_related в каждом списочном view и проверяйте число запросов через Django Debug Toolbar в разработке.
  3. Храните настройки в переменных окружения, запускайте manage.py check --deploy в CI и держите DEBUG=False во всех нелокальных окружениях.
  4. Добавляйте type hints и запускайте Ruff плюс mypy или Pyright в CI; они ловят много багов, которые скрывает динамическая типизация.
  5. С первого дня используйте кастомную модель пользователя (AUTH_USER_MODEL); менять её потом больно.
  6. В FastAPI никогда не вызывайте блокирующие библиотеки внутри async def-обработчиков; используйте def-обработчики или асинхронные драйверы.
  7. Пишите тесты на pytest и pytest-django; используйте фабрики вместо больших фикстур.

Частые ошибки

  • N+1-запросы из-за обращения к связанным объектам в циклах или шаблонах без select_related или prefetch_related.
  • Запуск runserver или fastapi dev в продакшене или DEBUG=True, из-за чего утекают настройки и стектрейсы.
  • Вызов синхронного драйвера базы или requests.get внутри async-обработчика FastAPI, что замораживает event loop для всех пользователей.
  • Правка старых миграций, уже применённых в продакшене, вместо добавления новых.
  • Медленная работа (письма, PDF, внешние API) внутри запроса вместо фонового worker-а.

Глубже

08Как на самом деле выполняются QuerySet

QuerySet ленивый: filter, exclude и order_by просто строят внутреннее дерево запроса. SQL выполняется, когда вы итерируете, вызываете list(), len(), bool(), делаете срез с шагом или обращаетесь по индексу. После первого вычисления результаты кэшируются в этом объекте QuerySet, поэтому двойная итерация обращается к базе один раз, но повторный вызов .all() создаёт новый QuerySet и новый запрос. exists() и count() выполняют дешёвые целевые запросы, так что используйте их вместо len(), когда нужно только число. Для огромных таблиц .iterator(chunk_size=2000) стримит строки, не кэшируя их все в памяти. Выражения F() переносят арифметику в SQL и избавляют от гонок, а annotate с Subquery или агрегатами оставляет логику отчётов в базе.

python
from django.db.models import F, Count, Q
 
# Atomic increment in SQL, no read-modify-write race
Product.objects.filter(pk=42).update(stock=F("stock") - 1)
 
# Aggregate in the database, not in Python
Category.objects.annotate(
    active=Count("products", filter=Q(products__price__gt=0))
).order_by("-active")
 
# Stream a large export
for p in Product.objects.only("id", "name").iterator(chunk_size=2000):
    ...

09Транзакции, блокировки и консистентность

По умолчанию Django работает в режиме autocommit: каждый запрос коммитится сам по себе. Оборачивайте многошаговые изменения в transaction.atomic(), чтобы они проходили или откатывались вместе. Для блокировок строк select_for_update() внутри atomic выполняет SELECT ... FOR UPDATE — так безопасно уменьшают остаток на складе или переводят деньги между счетами. Побочные эффекты, например отправка письма или постановка задачи в очередь, должны выполняться только после коммита; transaction.on_commit() делает именно это, так что worker никогда не обработает строку, которая была откачена. ATOMIC_REQUESTS оборачивает каждый запрос в транзакцию — это просто, но держит транзакции открытыми во время медленных view, поэтому многие команды предпочитают явные atomic-блоки.

python
from django.db import transaction
 
def reserve(product_id: int, qty: int) -> None:
    with transaction.atomic():
        p = Product.objects.select_for_update().get(pk=product_id)
        if p.stock < qty:
            raise ValueError("Out of stock")
        p.stock -= qty
        p.save(update_fields=["stock"])
        transaction.on_commit(lambda: notify_warehouse.enqueue(product_id))

10Асинхронность в Python: что на самом деле выполняется параллельно

asyncio в Python выполняет корутины в однопоточном event loop. Конкурентность возникает за счёт ожидания I/O: пока один запрос ждёт сеть, выполняется другой. Блокирующий вызов (time.sleep, синхронный драйвер БД, requests) останавливает весь loop. FastAPI решает это аккуратно: обычные def-эндпоинты выполняются в пуле потоков, async def-эндпоинты — в loop. Async view в Django работают, и ORM предоставляет асинхронные методы (aget, acreate, async for), но слой базы данных по-прежнему в основном выполняется в потоке через sync_to_async, поэтому асинхронный Django в основном выигрывает на I/O fan-out и долгоживущих соединениях, а не на чистой пропускной способности запросов. Для работы, упирающейся в CPU, нужны процессы или нативный код; Python 3.13 и 3.14 предлагают опциональную free-threaded сборку без GIL, но поддержка в библиотеках ещё созревает.

11Масштабирование и усиление защиты Django-деплоя

Django масштабируется горизонтально: приложение stateless, если сессии и кэш живут в Redis или базе, так что вы добавляете процессы Gunicorn и серверы за балансировщиком нагрузки. Первым узким местом становится количество соединений; задайте CONN_MAX_AGE для постоянных соединений, используйте нативный пул соединений PostgreSQL в Django 5.1+ или PgBouncer перед базой. Кэшируйте дорогие фрагменты и view через cache framework на базе Redis. Read-реплики поддерживаются через database routers. По безопасности включите SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE и HSTS, настраивайте SECURE_PROXY_SSL_HEADER только если ваш прокси выставляет этот заголовок, и держите админку за необычным URL с MFA.

python
# settings/production.py (excerpt)
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
DATABASES["default"]["CONN_MAX_AGE"] = 60
CACHES = {"default": {"BACKEND": "django.core.cache.backends.redis.RedisCache",
                      "LOCATION": "redis://127.0.0.1:6379/1"}}

Реальные ситуации

01

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

Проблема

Страница списка в Django, которая загружалась за 200 ms, теперь грузится три секунды. В инфраструктуре ничего не менялось, но шаблон теперь показывает категорию и поставщика каждого товара.

Как решить

Поставьте Django Debug Toolbar локально и откройте страницу: она покажет сотни почти одинаковых запросов — паттерн N+1. Добавьте select_related('category', 'supplier') в QuerySet view и prefetch_related для many-to-many тегов. Используйте only() или defer(), если в таблице есть большие текстовые колонки, которые не нужны списку. Добавьте тест с assertNumQueries, чтобы число запросов не могло регрессировать. Наконец, проверьте, что у колонок фильтрации и сортировки есть составной индекс, подтвердив это через EXPLAIN ANALYZE в PostgreSQL. Страница должна вернуться к нескольким запросам.

02

Отдача ML-модели рядом с существующим Django-приложением

Проблема

У команды данных есть модель на Python, которая должна отвечать предсказаниями по HTTP с низкой задержкой. Основное Django-приложение синхронное и уже нагружено.

Как решить

Вынесите модель в небольшой сервис на FastAPI, а не держите внутри Django. Загружайте модель один раз при старте через lifespan-обработчик, опишите Pydantic-схемы входа и выхода и запускайте под Uvicorn с числом worker-ов по CPU и памяти. Если инференс тяжёлый для CPU, используйте def-эндпоинты или пул процессов, чтобы event loop оставался свободным. Django вызывает сервис по HTTP с коротким таймаутом и fallback-ом или ставит пакетные предсказания в очередь через Celery. Так масштабирование, деплои и сбои модели остаются независимыми от основного продукта.

03

Сотрудникам нужен back office к следующей неделе

Проблема

У клиента есть база заказов, клиентов и товаров, и сотрудникам нужно искать, редактировать и экспортировать данные. Бюджета на кастомный интерфейс админки нет.

Как решить

Опишите таблицы моделями Django (или используйте inspectdb на существующей схеме), затем зарегистрируйте каждую модель в админке с list_display, list_filter, search_fields и list_select_related. Добавьте кастомные admin actions для экспорта и смены статусов, readonly_fields для колонок аудита и группы с детальными правами, чтобы сотрудники поддержки не могли удалять заказы. Защитите админку через HTTPS, нестандартный путь и MFA с помощью пакета вроде django-otp. Это за несколько дней даёт рабочий back office с учётом прав, а кастомный интерфейс можно сделать позже, если понадобится.

Шпаргалка

Новый проект с uv
uv init app && uv add django && uv run django-admin startproject config .
Создать и применить миграции
python manage.py makemigrations && python manage.py migrate
Показать SQL миграции
python manage.py sqlmigrate catalog 0002
Интерактивный ORM shell
python manage.py shell
Избежать N+1 (FK / M2M)
Model.objects.select_related("fk").prefetch_related("m2m")
Проверка готовности к продакшену
python manage.py check --deploy
Запустить Django под Gunicorn
gunicorn config.wsgi:application --workers 4 --bind 127.0.0.1:8000
Запустить FastAPI в режиме dev
uv add "fastapi[standard]" && uv run fastapi dev main.py
Линтинг и форматирование Python
ruff check . --fix && ruff format .

Вопросы и ответы

Q1Django или FastAPI: как вы выбираете?

Я начинаю с того, что нужно продукту. Если это полноценное приложение с пользователями, правами, back office и большим количеством реляционных данных, выигрывает Django, потому что ORM, миграции, админка и auth уже на месте и хорошо интегрированы. Если это сфокусированный JSON-сервис, например эндпоинт ML-инференса или внутренний API с высокой конкурентностью, лучше подходит FastAPI: асинхронность по умолчанию, валидация через Pydantic и сгенерированная OpenAPI-документация. Цена FastAPI в том, что всё остальное вы собираете сами. Django Ninja — хороший компромисс, когда нужна типизация в стиле FastAPI поверх Django.

Q2Что такое проблема N+1 в Django и как её исправить?

Она возникает, когда вы загружаете список объектов одним запросом, а затем обращаетесь к связи у каждого из них, что вызывает по дополнительному запросу на строку. Пятьдесят товаров с product.category в шаблоне превращаются в пятьдесят один запрос. Решение — select_related для связей ForeignKey и OneToOne, который добавляет SQL JOIN, и prefetch_related для many-to-many и обратных связей, который выполняет один дополнительный запрос и склеивает данные в Python. Я проверяю это через Django Debug Toolbar или assertNumQueries в тестах, чтобы регрессии ловились до продакшена.

Q3Что такое миграции и как их безопасно деплоить?

Миграции — это версионированные Python-файлы, описывающие изменения схемы; makemigrations генерирует их из изменений моделей, а migrate применяет. Они живут в Git и выполняются по порядку. Чтобы деплоить безопасно, я держу изменения обратно совместимыми: сначала добавляю nullable-колонки, заполняю данные, затем делаю их обязательными в следующем релизе и никогда не переименовываю и не удаляю колонку, которую ещё читает работающий код. С большими таблицами нужна осторожность, потому что некоторые операции блокируют таблицу; в PostgreSQL я добавляю индексы concurrently. Миграции выполняются отдельным шагом деплоя до того, как новый код начнёт принимать трафик.

Q4В чём разница между WSGI и ASGI?

Оба — стандарты Python, описывающие, как веб-сервер общается с приложением. WSGI — более старый синхронный: каждый запрос занимает worker на всё время своей обработки, что нормально для классических request-response приложений под Gunicorn. ASGI — асинхронный преемник: один процесс может обслуживать много параллельных соединений, пока ждёт I/O, и поддерживает WebSocket и долгоживущие соединения. FastAPI работает только на ASGI; Django поддерживает оба и имеет async view и асинхронный интерфейс ORM, хотя большая часть ORM под капотом всё ещё выполняется синхронно в отдельном потоке.

Q5Как Django защищает от распространённых веб-атак?

Несколько защит включены по умолчанию. Шаблонизатор экранирует вывод, что блокирует большую часть XSS. CSRF middleware требует токен в небезопасных запросах форм. ORM параметризует запросы, поэтому SQL-инъекция возможна только если вы собираете сырой SQL через форматирование строк. Защита от clickjacking выставляет X-Frame-Options, хеширование паролей использует стойкие алгоритмы, а Django 6.0 добавил встроенную поддержку Content Security Policy. Ваша задача — держать DEBUG выключенным, задать ALLOWED_HOSTS, включить secure cookies и HSTS за HTTPS и запускать manage.py check --deploy, чтобы увидеть, чего не хватает.

Q6Как запускать фоновые задачи в веб-приложениях на Python?

Всё медленное или ненадёжное — письма, обработка изображений, вызовы сторонних сервисов — должно уходить из запроса. Давно устоявшийся выбор — Celery с Redis или RabbitMQ в качестве брокера: он поддерживает повторы, расписание и маршрутизацию по разным очередям. RQ и Dramatiq — более простые альтернативы. Django 6.0 добавил встроенный tasks framework, который определяет стандартный API для постановки работы в очередь, а бэкенды предоставляют сам worker. Независимо от инструмента задачи должны быть идемпотентными, получать ID вместо целых объектов и иметь таймауты и лимиты повторов.

Вопросы уровня senior

S1Когда бы вы разделили Django-монолит и как?

Только когда есть конкретное давление: компонент с совсем другими потребностями в масштабировании, отдельная команда, которую блокируют общие релизы, или жёсткое требование изоляции. Перед разделением я ввожу границы внутри монолита: каждый Django app владеет своими моделями, другие app вызывают небольшой сервисный модуль вместо прямого импорта моделей, а внешние ключи между app сводятся к минимуму. Это делает последующее выделение дешёвым. Когда часть выделяется, она получает свою базу и общается с монолитом через API или события, а не через общие таблицы. Часто правильный первый шаг — просто вынести фоновую работу в worker-ы Celery, а горячий путь чтения — в кэш: это снимает большую часть давления без издержек распределённой системы.

S2Как вы делаете изменения схемы без простоя на большой таблице PostgreSQL в Django?

Я следую подходу expand and contract. Первый релиз: добавляю новую колонку как nullable без default, который переписывает таблицу, и деплою код, который пишет и в старую, и в новую колонку. Затем заполняю данные пачками через management command или задачу с ограничением скорости, чтобы не было лага репликации. Следующий релиз: переключаю чтение на новую колонку, затем добавляю NOT NULL через подход с валидированным check constraint. Финальный релиз: удаляю старую колонку, когда её уже не читает ни один работающий код. Индексы создаю через AddIndexConcurrently из django.contrib.postgres внутри миграции с atomic = False. Для каждой рискованной миграции я смотрю вывод sqlmigrate и задаю lock_timeout, чтобы заблокированный ALTER быстро падал, а не выстраивал весь трафик в очередь за собой.

S3Стоит ли использовать асинхронный Django, или высококонкурентную работу лучше отдать FastAPI?

Зависит от того, куда уходит время. Асинхронный Django полезен для fan-out на несколько внешних API, стриминговых ответов и WebSocket через Channels — при этом сохраняются админка, ORM и auth. Но асинхронный API ORM в основном оборачивает синхронные драйверы в потоки, а смешивание с синхронными middleware вызывает переключения контекста, так что нагрузка, упирающаяся в базу, почти ничего не выигрывает по сравнению с хорошо настроенными worker-ами Gunicorn. FastAPI с асинхронным драйвером вроде asyncpg даёт по-настоящему неблокирующий I/O базы и меньшие накладные расходы на запрос, но auth, админку и миграции придётся строить самому. Моё правило: продукт остаётся в Django, а конкретные высококонкурентные эндпоинты выносятся в сервис на FastAPI только тогда, когда измерения показывают, что узкое место — синхронные worker-ы.

S4Как бы вы защитили DRF API, которым пользуются мобильное приложение и SPA?

Для собственного SPA на том же сайте проще всего сессионная аутентификация с CSRF-защитой и secure, HttpOnly, SameSite cookies — так токены не хранятся в JavaScript. Для мобильного приложения лучше подходят короткоживущие access-токены с refresh-токенами (например, через djangorestframework-simplejwt или OAuth2-провайдер) с ротацией и отзывом при выходе. Каждый viewset получает явные permission-классы и проверки на уровне объектов, в идеале — ограниченные queryset вроде Order.objects.filter(owner=request.user), чтобы данные других пользователей были недоступны by design. Добавьте throttling по пользователю и по IP, строгую валидацию в сериализаторах, лимиты пагинации, CORS только для известных origin и логирование неудачных попыток аутентификации. И наконец, тестируйте пути авторизации, а не только happy path.

Глоссарий

Виртуальное окружение
Изолированная папка со своим интерпретатором Python и пакетами, чтобы проекты не делили и не ломали зависимости друг друга.
ORM
Object-relational mapper: позволяет запрашивать и сохранять строки базы данных как Python-объекты, а не писать SQL вручную.
QuerySet
Ленивое цепочечное представление запроса к базе в Django; оно выполняется только тогда, когда нужны результаты.
Миграция
Версионированный файл, описывающий изменение схемы; миграции применяются по порядку, чтобы каждая база оставалась синхронной с моделями.
Serializer (DRF)
Превращает экземпляры моделей в JSON и валидирует входящие данные до того, как они попадут в базу.
Pydantic
Библиотека, которая валидирует и разбирает данные с помощью type hints Python; ядро обработки запросов в FastAPI.
WSGI / ASGI
Стандартные интерфейсы между Python-приложениями и веб-серверами; WSGI синхронный, ASGI поддерживает async и WebSocket.
GIL
Global Interpreter Lock: в стандартных сборках CPython позволяет только одному потоку выполнять байткод Python в каждый момент времени.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.