Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Оптимизация производительности
optimization

Оптимизация производительности

select_related, prefetch_related, оптимизация запросов, кэширование

Оптимизация производительности в Django REST Framework

Производительность API критична для пользовательского опыта. DRF и Django предоставляют инструменты для оптимизации запросов, кэширования и ускорения ответов.

#Проблема N+1 запросов

N+1 проблема — антипаттерн, когда для получения N связанных объектов выполняется N+1 запрос к БД.

# ПЛОХО: N+1 запросов class ArticleSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source='author.username', read_only=True) class Meta: model = Article fields = ['id', 'title', 'author_name'] class ArticleList(generics.ListAPIView): queryset = Article.objects.all() # 1 запрос + N запросов к author serializer_class = ArticleSerializer

Решение: select_related для ForeignKey и OneToOne:

# ХОРОШО: 2 запроса class ArticleList(generics.ListAPIView): queryset = Article.objects.select_related('author').all() serializer_class = ArticleSerializer

#select_related

Оптимизирует ForeignKey и OneToOne связи через SQL JOIN:

# Один запрос вместо N+1 queryset = Article.objects.select_related('author').all() # Несколько связанных полей queryset = Article.objects.select_related('author', 'category').all() # Вложенные связи queryset = Article.objects.select_related( 'author__profile', 'category__parent' ).all()

Когда использовать:

  • ForeignKey (многие-к-одному)
  • OneToOneField (один-к-одному)

#prefetch_related

Оптимизирует ManyToMany и reverse ForeignKey через отдельный запрос:

# ПЛОХО: N+1 для many-to-many class ArticleSerializer(serializers.ModelSerializer): tags = TagSerializer(many=True, read_only=True) class Meta: model = Article fields = ['id', 'title', 'tags'] class ArticleList(generics.ListAPIView): queryset = Article.objects.all() # 1 + N запросов к tags serializer_class = ArticleSerializer # ХОРОШО: 2 запроса class ArticleList(generics.ListAPIView): queryset = Article.objects.prefetch_related('tags').all() serializer_class = ArticleSerializer

Когда использовать:

  • ManyToManyField
  • Reverse ForeignKey (доступ через related_name)
# Reverse ForeignKey queryset = Author.objects.prefetch_related('articles').all() # Несколько связей queryset = Article.objects.prefetch_related( 'tags', 'comments', 'comments__author' # Вложенный prefetch ).all()

#Разница между select_related и prefetch_related

МетодТип связиМеханизмКогда использовать
select_relatedForeignKey, OneToOneSQL JOINСвязь «к одному»
prefetch_relatedManyToMany, reverse FKОтдельный запрос + JOIN в PythonСвязь «ко многим»
# Комбинирование обоих методов queryset = Article.objects.select_related( 'author', # FK → JOIN 'category' # FK → JOIN ).prefetch_related( 'tags', # M2M → отдельный запрос 'comments' # reverse FK → отдельный запрос ).all()

#only() и defer()

Загрузка только нужных полей:

# Загрузить только указанные поля queryset = Article.objects.only('id', 'title', 'created_at') # Отложить загрузку больших полей queryset = Article.objects.defer('content', 'summary') # Комбинирование queryset = Article.objects.select_related('author').only( 'id', 'title', 'author_id' )

Внимание: При обращении к отложенным полям будет выполнен дополнительный запрос.

#Оптимизация в ViewSet

class ArticleViewSet(viewsets.ModelViewSet): serializer_class = ArticleSerializer def get_queryset(self): queryset = Article.objects.select_related( 'author', 'category' ).prefetch_related( 'tags', Prefetch( 'comments', queryset=Comment.objects.select_related('author').order_by('-created_at') ) ) # Фильтрация для не-админов if not self.request.user.is_staff: queryset = queryset.filter(is_published=True) return queryset def get_serializer_context(self): context = super().get_serializer_context() context['request'] = self.request return context

#Кэширование

#Кэширование на уровне view

from django.utils.cache import patch_response_headers from django.views.decorators.cache import cache_page from rest_framework.response import Response class ArticleDetail(generics.RetrieveAPIView): queryset = Article.objects.select_related('author').all() serializer_class = ArticleSerializer def retrieve(self, request, *args, **kwargs): response = super().retrieve(request, *args, **kwargs) patch_response_headers(response, cache_timeout=3600) # 1 час return response # Или через декоратор @api_view(['GET']) @cache_page(60 * 15) # 15 минут def cached_article_list(request): articles = Article.objects.all() serializer = ArticleSerializer(articles, many=True) return Response(serializer.data)

#Кэширование в сериализаторе

from django.core.cache import cache class ArticleSerializer(serializers.ModelSerializer): author_name = serializers.SerializerMethodField() def get_author_name(self, obj): # Кэширование имени автора cache_key = f'author_name_{obj.author_id}' name = cache.get(cache_key) if name is None: name = obj.author.username cache.set(cache_key, name, 3600) return name class Meta: model = Article fields = ['id', 'title', 'author_name']

#Кэширование queryset

from django.core.cache import cache def get_cached_articles(): cache_key = 'articles_list' articles = cache.get(cache_key) if articles is None: articles = list(Article.objects.select_related('author').all()) cache.set(cache_key, articles, 300) # 5 минут return articles

#Пагинация для больших данных

Всегда используйте пагинацию для больших наборов данных:

# settings.py REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 'PAGE_SIZE': 20, } # CursorPagination предпочтительнее для больших данных class ArticlePagination(CursorPagination): ordering = '-created_at' page_size = 20 page_size_query_param = 'page_size' max_page_size = 100

#Оптимизация сериализаторов

#Избегайте вложенных сериализаторов для списков

# ПЛОХО: медленно для больших списков class ArticleSerializer(serializers.ModelSerializer): author = AuthorSerializer(read_only=True) # Вложенный сериализатор class Meta: model = Article fields = ['id', 'title', 'author'] # ХОРОШО: быстрее class ArticleListSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source='author.username', read_only=True) author_id = serializers.IntegerField(source='author.id', read_only=True) class Meta: model = Article fields = ['id', 'title', 'author_id', 'author_name']

#Используйте read_only поля

class ArticleSerializer(serializers.ModelSerializer): # Вычисляемые поля только для чтения comment_count = serializers.IntegerField(read_only=True) is_recent = serializers.SerializerMethodField(read_only=True) class Meta: model = Article fields = ['id', 'title', 'comment_count', 'is_recent'] def get_is_recent(self, obj): return obj.created_at > timezone.now() - timedelta(days=7)

#Database индексы

Добавьте индексы для полей, по которым идёт фильтрация и сортировка:

class Article(models.Model): title = models.CharField(max_length=200, db_index=True) created_at = models.DateTimeField(auto_now_add=True, db_index=True) slug = models.SlugField(unique=True) # Автоматический индекс class Meta: indexes = [ models.Index(fields=['-created_at']), models.Index(fields=['author', '-created_at']), models.Index(fields=['category', 'is_published']), ]

#Асинхронные view

Django 3.1+ поддерживает async view:

from rest_framework.response import Response from rest_framework.views import APIView import asyncio class AsyncArticleView(APIView): async def get(self, request, pk): # Асинхронный запрос к БД article = await sync_to_async(Article.objects.get)(pk=pk) serializer = ArticleSerializer(article) return Response(serializer.data)

#Профилирование и мониторинг

#Django Debug Toolbar

pip install django-debug-toolbar

Показывает количество SQL запросов и время выполнения.

#django-silk

pip install django-silk

Профилирование запросов, SQL запросы, время выполнения.

#Логирование медленных запросов

# settings.py LOGGING = { 'version': 1, 'handlers': { 'console': {'class': 'logging.StreamHandler'}, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG', # Логирует все SQL запросы }, }, }

#Best Practices

  1. Всегда используйте select_related/prefetch_related — решает 90% проблем производительности
  2. Пагинация обязательна — не возвращайте все записи
  3. Кэшируйте дорогие запросы — используйте Redis/Memcached
  4. Индексируйте поля фильтрации — db_index=True, Meta.indexes
  5. Профилируйте запросы — Django Debug Toolbar, django-silk
  6. Избегайте вложенных сериализаторов для списков — используйте простые поля
  7. Мониторьте медленные запросы — логируйте запросы дольше 100ms

Далее: Best Practices