← Назад к вопросам
Что будешь делать, если эндпоинты 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})
Заключение
Медленные эндпоинты — это всегда комбинация нескольких проблем. Моя методология:
- Профилировать (Debug Toolbar, silk, cProfile)
- Найти узкое место (обычно БД)
- Оптимизировать (индексы, select_related, кеш)
- Проверить результат (должно быть < 200ms)
- Мониторить (django-silk, Sentry, APM инструменты)
Основное правило: не гадай, измеряй.