Django ORM: налаштування та оптимізація для Python-застосунків

Ваш Django-сайт на PostgreSQL гальмує на кожній сторінці, а користувачі йдуть, не дочекавшись відповіді? Ми налаштовуємо Django ORM під ключ: усуваємо N+1 запити, додаємо індекси та налаштовуємо пул з'єднань. Наша команда бере на себе весь цикл — від аудиту до впровадження, забезпечуючи стабільну роботу та подальшу підтримку.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

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

Django ORM: налаштування та оптимізація для Python-застосунків

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

Ми, інженери з 10-річним досвідом (понад 100 реалізованих проєктів, 5 років на ринку), налаштовуємо Django ORM під ключ. Гарантуємо зниження TTFB мінімум на 50% — інакше повернемо гроші. Аудимо поточну схему, усуваємо N+1, виставляємо оптимальні індекси, налаштовуємо persistent connections та реплікацію. Результат: час відповіді бази знижується до 10 разів, LCP падає на 60%, а вартість підтримки скорочується вдвічі. Суттєва економія на хмарних ресурсах — реальний кейс одного з наших клієнтів з каталогом на 50 000 товарів, де щомісячна економія склала $500. Отримайте план покращень за 1 день — просто напишіть нам.

Основні проблеми, які вирішуємо

  • N+1 запити: кастомний менеджер краще за ручний виклик у 10-50 разів за кількістю запитів.
  • Відсутність індексів: додаємо складені індекси під часті фільтри.
  • Хоча б один неефективний запит на сторінку — анотації та агрегації зводять їх до мінімуму.
  • Відсутність persistent connections — кожне з'єднання відкривається заново, збільшуючи latency у 5 разів. Persistent connections вирішують це, скорочуючи затримку до 5 разів порівняно з відкриттям нового з'єднання на кожен запит.
  • Немає реплікації — мастер БД перевантажений. Налаштовуємо автоматичне маршрутизацію читання на репліку, розвантажуючи мастер.

Налаштування підключення до PostgreSQL

За рекомендацією Django Documentation, persistent connections значно знижують latency. У 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 — ми відповімо на всі питання. Для початку замовте аудит поточної схеми: ми проаналізуємо запити, індекси та з'єднання, після чого надамо детальний звіт з рекомендаціями.

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

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060