ГлавнаяБлогУправление памятью в Django: что происходит под капотом
Python

Управление памятью в Django: что происходит под капотом

Узнайте, как Django управляет памятью: разница между startup и каждым запросом, типичные ошибки с глобальными переменными и правила безопасного кода.

Al
Редакция Algolitalgolit.ru
12 мин чтения14 июня 2026 г.

Как Django управляет памятью: стартап против каждого запроса

Если вы перешли с PHP, вы привыкли, что каждый запрос обрабатывается с нуля. В Python всё иначе: веб-сервер (например, Gunicorn) запускает один или несколько рабочих процессов, которые живут постоянно и обрабатывают множество запросов. В этой статье разберём, как устроена память в Django, чтобы избежать утечек и неожиданного поведения.

Две фазы выполнения Django

Когда вы запускаете Django-сервер (встроенный dev-сервер или Gunicorn), есть два этапа:

  • Startup (загрузка) — выполняется один раз при старте каждого рабочего процесса: запускается wsgi.py, вызывается get_wsgi_application(), загружаются настройки, импортируются приложения из INSTALLED_APPS, вызываются методы ready() конфигов приложений, компилируются URL-паттерны.
  • Обработка запроса — для каждого запроса: middleware-цепочка, резолвинг URL, выполнение view, работа контекстных менеджеров, рендеринг шаблона, формирование ответа.

Django dev-сервер использует один рабочий процесс с потоками. Gunicorn запускает несколько рабочих процессов (отдельных копий приложения). Обычная формула: (2 × число_ядер_CPU) + 1 рабочих процессов.

Демонстрация разделяемой памяти между запросами

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

from django.http import JsonResponse
from django.utils.timezone import now

TIMESTAMPS = []  # разделяемый список

def show_timestamps(request):
    TIMESTAMPS.append(now())
    return JsonResponse({'timestamps': TIMESTAMPS})

Результат после нескольких запросов (вручную отформатирован):

{
    "timestamps": [
        "2026-06-08T19:48:42.044Z",
        "2026-06-08T19:48:43.875Z",
        "2026-06-08T19:48:44.776Z"
    ]
}

Если запустить это с Gunicorn и несколькими рабочими процессами, каждый процесс будет иметь свой список TIMESTAMPS, поэтому данные не будут общими между разными воркерами.

Как управляется оперативная память

1. Состояние инициализации сохраняется навсегда

При старте Django в память процесса загружаются:

  • django.apps.registry.Apps (реестр приложений со всеми моделями)
  • Резолверы URL (urlpatterns)
  • Экземпляры middleware (если инициализированы при старте)
  • Кэшированные шаблонные движки
  • Конфигурации подключений к БД (не сами соединения)

Эта память остаётся выделенной до перезапуска рабочего процесса.

2. Состояние выполнения живёт только во время запроса

Каждый запрос создаёт объекты, локальные для стека вызовов: view, формы, queryset'ы, сериализаторы и т.д. После отправки ответа:

  • Сборщик мусора Python обычно освобождает эти объекты.
  • Django не выполняет явной очистки — он полагается на стандартное управление памятью Python.
  • Объекты, созданные в рамках запроса, удаляются только после того, как выходят из области видимости и на них не остаётся ссылок.

Типичные ошибки и как их избежать

1. Изменяемое состояние на уровне модуля

Изменяемый объект, определённый на уровне модуля, сохраняется между всеми запросами, обрабатываемыми одним рабочим процессом, и будет накапливать данные от каждого пользователя.

ПЛОХО:

_recent_users = []

def dashboard(request):
    # утечка между запросами!
    _recent_users.append(request.user.id)
    return render(request, 'dashboard.html', {'recent': _recent_users})

ХОРОШО — состояние хранится в БД, кэше или сессии:

def dashboard(request):
    recent_users = get_recent_users_from_db()
    return render(request, 'dashboard.html', {'recent': recent_users})

2. Хранение данных запроса в shared-экземпляре

Экземпляры middleware живут всё время жизни рабочего процесса. Если хранить что-то специфичное для запроса в self, это будет разделяться между всеми запросами.

ПЛОХО:

class AuditMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.current_user = None  # разделяется между запросами!

    def __call__(self, request):
        self.current_user = request.user
        return self.get_response(request)

ХОРОШО — прикрепляем данные к объекту запроса:

class AuditMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response  # только неизменяемая конфигурация

    def __call__(self, request):
        request.current_user = request.user  # живёт только в рамках запроса
        return self.get_response(request)

3. Кэш на уровне класса

Атрибут класса разделяется между всеми экземплярами этого класса, а значит, между всеми запросами. Данные первого пользователя могут быть кэшированы и отданы всем остальным.

ПЛОХО:

class ReportGenerator:
    _cache = {}  # разделяется между всеми экземплярами!

    def get_data(self, user):
        if 'data' not in self._cache:
            self._cache['data'] = expensive_query(user)
        return self._cache['data']

ХОРОШО — используем фреймворк кэширования с ключом, зависящим от пользователя:

from django.core.cache import cache

class ReportGenerator:
    def get_data(self, user):
        key = f'report:{user.id}'
        return cache.get_or_set(key, lambda: expensive_query(user), timeout=300)

Ментальная модель: где что хранить

Прежде чем писать код на уровне модуля, спросите себя: «Если 1000 разных пользователей обратятся к этому рабочему процессу, и эта переменная будет существовать для всех них, безопасно ли это?»

Место храненияВремя жизниБезопасно для пользовательских данных?
Локальная переменная в view/функцииОдин запрос✅ Да
Атрибуты объекта requestОдин запрос✅ Да
threading.local()Один поток✅ Да
БД / кэш с ключами, зависящими от пользователяПостоянно, но с ключом✅ Да
self в экземпляре middleware/классаВремя жизни рабочего процесса⚠️ Только неизменяемая конфигурация
Изменяемая переменная на уровне модуляВремя жизни рабочего процесса❌ Никогда не хранить пользовательские данные
Изменяемый атрибут классаВремя жизни рабочего процесса❌ Никогда не хранить пользовательские данные

Правило: неизменяемая конфигурация (числа, булевы значения, строки, байты, кортежи, frozenset и None) может жить на уровне модуля; всё, что зависит от запроса, пользователя или времени, должно находиться в рамках цикла запроса.

Практический вывод

Теперь вы знаете, что у Django-воркера две области памяти: состояние инициализации (живёт всё время жизни процесса) и состояние запроса (существует только во время обработки). Главное правило: неизменяемая конфигурация — на уровне модуля; всё, что зависит от пользователя или запроса — внутри view или на объекте request. Изменяемые глобальные переменные, атрибуты класса и состояние middleware — источник утечек памяти и уязвимостей. Проверьте свой код: уберите все изменяемые модульные списки и словари, замените их на хранилища с ключами пользователя. Ваше приложение станет надёжнее и безопаснее.

#Django#управление памятью#middleware#глобальные переменные#производительность
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →