Django ORM: настройка и оптимизация для Python-приложений

Представьте: ваш Django-сайт на PostgreSQL тормозит на каждой странице. N+1 запросы плодятся, индексы отсутствуют, пул соединений не настроен — TTFB > 2 секунд, Core Web Vitals провалены, пользователи уходят. Особенно больно на страницах каталога с тысячами товаров: один запрос категории выдёргивает

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Django ORM: настройка и оптимизация для Python-приложений
Средний
~1 день

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    983
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1245
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    998

Представьте: ваш Django-сайт на PostgreSQL тормозит на каждой странице. N+1 запросы плодятся, индексы отсутствуют, пул соединений не настроен — TTFB > 2 секунд, Core Web Vitals провалены, пользователи уходят. Особенно больно на страницах каталога с тысячами товаров: один запрос категории выдёргивает сотни подзапросов. Без правильной архитектуры ORM проект превращается в кашу из запросов, а цена каждой доработки растёт.

Мы, инженеры с 10-летним опытом, настраиваем Django ORM под ключ. Аудим текущую схему, устраняем N+1, выставляем оптимальные индексы, настраиваем persistent connections и репликацию. Результат: время ответа базы снижается до 10 раз, LCP падает на 60%, а стоимость поддержки сокращается вдвое. Существенная экономия на облачных ресурсах — реальный кейс одного из наших клиентов с каталогом на 50 000 товаров. Получите план улучшений за 1 день — просто напишите нам.

Основные проблемы, которые решаем

  • N+1 запросы: вместо 1 + N запросов делаем 2 (select_related/prefetch_related).
  • Отсутствие индексов: добавляем составные индексы под частые фильтры.
  • Хотя бы один неэффективный запрос на страницу — аннотации и агрегации сводят их к минимуму.
  • Отсутствие persistent connections — каждое соединение открывается заново, увеличивая latency в 5 раз. Persistent connections решают это, сокращая задержку до 5 раз по сравнению с открытием нового соединения на каждый запрос.
  • Нет репликации — мастер БД перегружен. Настраиваем автоматический роутинг чтения на реплику, разгружая мастер.

Настройка подключения к PostgreSQL

В settings.py определяем несколько баз при необходимости. Persistent connections снижают latency до 5 раз. Таблица настроек:

Параметр default replica
ENGINE django.db.backends.postgresql django.db.backends.postgresql
CONN_MAX_AGE 60 60
connect_timeout 10
TEST mirror default
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST', default='127.0.0.1'), 'PORT': env('DB_PORT', default='5432'), 'CONN_MAX_AGE': 60, 'OPTIONS': { 'connect_timeout': 10, 'options': '-c search_path=public', }, 'TEST': { 'NAME': 'test_myapp', }, }, 'replica': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_REPLICA_NAME'), 'USER': env('DB_REPLICA_USER'), 'PASSWORD': env('DB_REPLICA_PASSWORD'), 'HOST': env('DB_REPLICA_HOST'), 'PORT': '5432', 'CONN_MAX_AGE': 60, 'TEST': { 'MIRROR': 'default', }, }, } 

Почему кастомные менеджеры решают проблему N+1?

Кастомный менеджер — главный инструмент для инкапсуляции логики выборок. Он автоматически подгружает связанные объекты, исключая N+1 запросы. По сравнению с ручным вызовом select_related в каждом вью, кастомный менеджер уменьшает количество запросов в 10-50 раз и централизует логику.

Шаги реализации:

  1. Определите QuerySet с методами для фильтрации и жадной загрузки.
  2. Создайте менеджер, возвращающий этот QuerySet.
  3. Используйте менеджер в коде — цепочки методов читаются и поддерживаются.

На примере каталога: моделей Category и Product. Вот код моделей:

from django.db import models from django.utils.text import slugify class Category(models.Model): name = models.CharField(max_length=200) slug = models.SlugField(unique=True, max_length=220) parent = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.SET_NULL, related_name='children', ) class Meta: verbose_name_plural = 'categories' ordering = ['name'] def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class Product(models.Model): class Status(models.TextChoices): DRAFT = 'draft', 'Draft' PUBLISHED = 'published', 'Published' ARCHIVED = 'archived', 'Archived' title = models.CharField(max_length=500) slug = models.SlugField(unique=True, max_length=520) category = models.ForeignKey( Category, on_delete=models.PROTECT, related_name='products', ) price = models.DecimalField(max_digits=12, decimal_places=2) status = models.CharField( max_length=10, choices=Status.choices, default=Status.DRAFT, ) tags = models.ManyToManyField('Tag', blank=True, related_name='products') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: indexes = [ models.Index(fields=['status', '-created_at']), models.Index(fields=['category', 'status']), ] 

А вот готовый менеджер с QuerySet и пример его использования:

class PublishedProductQuerySet(models.QuerySet): def published(self): return self.filter(status=Product.Status.PUBLISHED) def with_category(self): return self.select_related('category') def with_tags(self): return self.prefetch_related('tags') def in_price_range(self, min_price, max_price): return self.filter(price__gte=min_price, price__lte=max_price) class ProductManager(models.Manager): def get_queryset(self): return PublishedProductQuerySet(self.model, using=self._db) def published(self): return self.get_queryset().published() # Использование: products = ( Product.objects.published() .with_category() .with_tags() .in_price_range(100, 5000) .order_by('-created_at')[:20] ) 

Оптимизация запросов с аннотациями

Аннотации и F-выражения позволяют выполнить агрегацию и обновление одним запросом без Python round-trip:

from django.db.models import Count, Avg, F, Q, ExpressionWrapper, DecimalField stats = ( Category.objects .annotate( product_count=Count('products', filter=Q(products__status='published')), avg_price=Avg('products__price', filter=Q(products__status='published')), ) .filter(product_count__gt=0) .order_by('-product_count') ) Product.objects.filter(status='published').update( price=ExpressionWrapper(F('price') * 1.1, output_field=DecimalField()) ) 

Этот подход сокращает количество запросов с нескольких десятков до одного, что напрямую влияет на TTFB.

Как настроить репликацию с автоматическим роутингом?

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

class ReadReplicaRouter: READ_DB = 'replica' WRITE_DB = 'default' def db_for_read(self, model, **hints): return self.READ_DB def db_for_write(self, model, **hints): return self.WRITE_DB def allow_relation(self, obj1, obj2, **hints): return True def allow_migrate(self, db, app_label, model_name=None, **hints): return db == self.WRITE_DB # settings.py DATABASE_ROUTERS = ['myapp.db_router.ReadReplicaRouter'] 

Шаги настройки:

  1. Создайте реплику PostgreSQL (физическую или логическую).
  2. Пропишите базы в DATABASES.
  3. Реализуйте роутер, как выше.
  4. Включите роутер в DATABASE_ROUTERS.

Правила миграций в продакшн

  • Добавление nullable-колонки не блокирует таблицу в PostgreSQL 11+.
  • Индексы создавайте только через CONCURRENTLY — Django сам использует его для PostgreSQL, что не блокирует таблицу при создании индекса. Это важно для продакшена.
  • Переименование колонки: в два этапа (добавить новую → скопировать данные → убрать старую).
  • --fake — только для синхронизации состояния без повторного SQL.

Что входит в настройку Django ORM под ключ?

Этап Описание Срок
Аудит текущей схемы Выявление N+1, дублирующих запросов, отсутствия индексов 1 день
Проектирование Определение стратегии индексов, репликации, менеджеров 0.5 дня
Реализация Настройка подключения, моделей, менеджеров, роутера 1–2 дня
Тестирование Нагрузочное тестирование, проверка производительности 0.5 дня
Документация и обучение ER-диаграмма, описание QuerySet, рекомендации 0.5 дня

Для повышения производительности Django ORM свяжитесь с нами — оценим проект и предложим план действий. Получите консультацию по оптимизации Django — мы ответим на все вопросы. Для начала закажите аудит текущей схемы: мы проанализируем запросы, индексы и соединения, после чего предоставим детальный отчёт с рекомендациями.