← Назад к вопросам

Что будешь делать, если эндпоинты Django-сайта обрабатываются в течение минуты?

3.0 Senior🔥 301 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Диагностика и оптимизация медленных Django endpoints

Ситуация, когда эндпоинты обрабатываются 1+ минуту, явно указывает на проблему. Я бы начал систематическую диагностику, используя проверенный подход.

Шаг 1: Определить узкое место

Использую Django Debug Toolbar для профилирования:

# settings.py для development
INSTALLED_APPS = [
    'debug_toolbar',
    # ... другие приложения
]

MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # ... другие middleware
]

if DEBUG:
    INTERNAL_IPS = ['127.0.0.1']
# urls.py
from django.conf import settings
if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns

Debug Toolbar показывает:

  • Количество SQL запросов
  • Время выполнения каждого
  • Параметры запросов
  • Stack trace проблемных операций

Также использую django-silk для production:

# Более лёгкий вариант, можно использовать в production
pip install django-silk

# Профилирование:
from silk.profiling.profiler import silk_profile

@silk_profile(name='expensive_operation')
def expensive_database_operation():
    # Операция будет залогирована
    return User.objects.filter(...).count()

Python встроенный профайлер:

import cProfile
import pstats
import io
from django.http import HttpResponse

def profile_view(request):
    pr = cProfile.Profile()
    pr.enable()
    
    # Код, который тестируем
    users = User.objects.all()
    for user in users:
        user.profile.process_data()
    
    pr.disable()
    s = io.StringIO()
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
    ps.print_stats(10)  # Top 10 функций
    
    return HttpResponse(f"<pre>{s.getvalue()}</pre>")

Шаг 2: Самые частые причины медленных запросов

Проблема 1: N+1 запросы к БД

# ❌ ПЛОХО: 1 запрос на users + 1000 запросов на profiles
def get_users_with_profiles():
    users = User.objects.all()
    for user in users:
        print(user.profile.bio)  # SQL запрос на каждой итерации!
    return users

# ✅ ХОРОШО: 1 запрос с JOIN
from django.db.models import Prefetch

def get_users_with_profiles():
    users = User.objects.select_related('profile').all()
    # 1 SQL запрос вместо 1000+
    return users

# Для reverse relationships (один ко многим):
users = User.objects.prefetch_related('posts').all()
# Или для более сложных случаев:
from django.db.models import Prefetch
posts_prefetch = Prefetch(
    'posts',
    Post.objects.filter(published=True).order_by('-created_at')[:5]
)
users = User.objects.prefetch_related(posts_prefetch).all()

Проблема 2: Слишком много данных из БД

# ❌ ПЛОХО: SELECT * с 50 полями, если нужны только 2
users = User.objects.all()
for user in users:
    print(user.username, user.email)

# ✅ ХОРОШО: Только нужные поля
users = User.objects.values_list('username', 'email')

# Или с only() для model instances:
users = User.objects.only('username', 'email')

# Исключить тяжелые поля:
users = User.objects.defer('bio', 'profile_picture')

Проблема 3: Отсутствие индексов в БД

# models.py
class User(models.Model):
    email = models.EmailField(unique=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    status = models.CharField(
        max_length=20,
        db_index=True  # Часто фильтруем по status
    )
    
    class Meta:
        indexes = [
            models.Index(fields=['created_at', 'status']),  # Composite index
        ]

# Проверить отсутствующие индексы:
# python manage.py sqlsequencereset app_name | psql
# EXPLAIN ANALYZE SELECT * FROM app_user WHERE status = 'active';

Проблема 4: Неэффективные вычисления в коде

# ❌ ПЛОХО: Вычисления в Python для каждой строки
users = User.objects.all()
total = 0
for user in users:
    total += user.calculate_score()  # Медленная операция на каждого

# ✅ ХОРОШО: Агрегация в БД
from django.db.models import F, Sum, Case, When

total_score = User.objects.aggregate(
    total=Sum(
        Case(
            When(status='active', then=F('score')),
            default=0
        )
    )
)['total']

Проблема 5: Отсутствие кеширования

# ✅ ХОРОШО: Использовать кеш для часто запрашиваемых данных
from django.views.decorators.cache import cache_page
from django.core.cache import cache

# Кеш на уровне view
@cache_page(60 * 15)  # 15 минут
def expensive_view(request):
    users = User.objects.all()
    return JsonResponse(list(users.values()))

# Кеш на уровне функции
def get_popular_posts():
    cache_key = 'popular_posts'
    posts = cache.get(cache_key)
    
    if posts is None:
        posts = Post.objects.filter(likes__gte=100).order_by('-created_at')[:10]
        cache.set(cache_key, posts, 60 * 60)  # 1 час
    
    return posts

Шаг 3: Полный чеклист оптимизации

# settings.py для production

# 1. Включить persistent connections
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'CONN_MAX_AGE': 600,  # Connection pooling
    }
}

# 2. Использовать connection pooling (pgBouncer или Django-db-connection-pool)
pip install django-db-connection-pool

# 3. Кеширование
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    }
}

# 4. Query optimization
DEBUG = False  # В production!

# 5. Асинхронные задачи для тяжёлых операций
# pip install celery redis
from celery import shared_task

@shared_task
def process_user_data(user_id):
    user = User.objects.get(id=user_id)
    # Тяжелые вычисления в фоне
    return user.process_complex_algorithm()

# View
def trigger_processing(request, user_id):
    process_user_data.delay(user_id)
    return JsonResponse({'status': 'processing'})

# 6. Пагинация вместо загрузки всех данных
from django.core.paginator import Paginator

users = User.objects.all()
paginator = Paginator(users, 100)  # 100 на странице
page = paginator.get_page(request.GET.get('page', 1))
return render(request, 'users.html', {'page': page})

Пример полной оптимизации

# ❌ БЫЛО: 1+ минута
from django.shortcuts import render

def user_dashboard(request):
    users = User.objects.all()  # 1000+ users
    data = []
    for user in users:
        profile = user.profile  # N+1 запрос
        posts = user.posts.all()  # Ещё N запросов!
        total = sum(p.likes for p in posts)  # И в памяти!
        data.append({
            'user': user.username,
            'bio': profile.bio,
            'posts_count': len(posts),
            'total_likes': total
        })
    return render(request, 'dashboard.html', {'data': data})

# ✅ СТАЛО: < 100ms
from django.db.models import Count, Sum
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def user_dashboard(request):
    users = User.objects.select_related('profile').prefetch_related(
        'posts'
    ).annotate(
        posts_count=Count('posts'),
        total_likes=Sum('posts__likes')
    ).values('username', 'profile__bio', 'posts_count', 'total_likes')
    
    data = list(users[:100])  # Пагинация
    return render(request, 'dashboard.html', {'data': data})

Заключение

Медленные эндпоинты — это всегда комбинация нескольких проблем. Моя методология:

  1. Профилировать (Debug Toolbar, silk, cProfile)
  2. Найти узкое место (обычно БД)
  3. Оптимизировать (индексы, select_related, кеш)
  4. Проверить результат (должно быть < 200ms)
  5. Мониторить (django-silk, Sentry, APM инструменты)

Основное правило: не гадай, измеряй.

Что будешь делать, если эндпоинты Django-сайта обрабатываются в течение минуты? | PrepBro