| name | django-performance |
| description | Esta skill deve ser usada quando o usuário está escrevendo queries do ORM, views, serializers ou qualquer código Django onde performance é relevante. Detecta problemas N+1, sugere otimizações de queries e boas práticas de cache. |
| version | 1.0.0 |
Performance Django — Claude Code Skill
Esta skill do Claude Code fornece orientação automática de performance para projetos Django, focando em queries do ORM, views, serializers e cache.
Quando esta skill se aplica
Ativa quando a solicitação do usuário envolve:
- Escrever ou modificar queries do ORM Django
- Criar views que acessam o banco de dados
- Escrever serializers que acessam relacionamentos
- Trabalhar com listagens, paginação ou filtros
- Configurar cache
- Otimizar endpoints lentos
- Trabalhar com Celery/tasks assíncronas
- Criar management commands que processam dados em massa
Problema N+1 — Detecção e Correção
O problema N+1 é o vilão de performance mais comum em Django.
Como identificar
Padrão perigoso: view ou serializer acessa campos de relacionamento (FK, O2O, M2M) sem select_related ou prefetch_related no queryset.
posts = Post.objects.all()
for post in posts:
print(post.author.name)
posts = Post.objects.select_related("author").all()
Regras de uso
| Tipo de relacionamento | Método | Quando usar |
|---|
| ForeignKey (acesso direto) | select_related | Sempre que acessar o campo FK |
| OneToOneField | select_related | Sempre que acessar o campo O2O |
| ManyToManyField | prefetch_related | Sempre que acessar o campo M2M |
| ForeignKey reversa | prefetch_related | Quando acessar model_set ou related_name |
| Cadeia de FKs | select_related("fk1__fk2") | Para JOINs encadeados |
Em serializers DRF
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.name")
class Meta:
model = Post
fields = ["id", "title", "author_name"]
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related("author").all()
serializer_class = PostSerializer
Nested serializers
class PostSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, source="comments_set")
tags = TagSerializer(many=True)
queryset = Post.objects.prefetch_related(
"comments_set",
"tags",
Prefetch("comments_set", queryset=Comment.objects.select_related("author")),
).select_related("author")
Otimização de queries
Use .only() e .defer() para limitar campos
posts = Post.objects.all()
posts = Post.objects.only("id", "title", "created_at")
posts = Post.objects.defer("body", "metadata")
Use .values() e .values_list() para consultas leves
titles = Post.objects.values_list("title", flat=True)
from django.db.models import Count
author_counts = Post.objects.values("author").annotate(total=Count("id"))
Use .exists() em vez de .count() para verificação
if Post.objects.filter(published=True).count() > 0:
...
if Post.objects.filter(published=True).exists():
...
Use .update() e .delete() em massa
for post in Post.objects.filter(published=False):
post.published = True
post.save()
Post.objects.filter(published=False).update(published=True)
Use bulk_create() e bulk_update()
for item in data:
Model.objects.create(**item)
Model.objects.bulk_create([Model(**item) for item in data], batch_size=1000)
Use objetos F() para operações atômicas
from django.db.models import F
post = Post.objects.get(pk=1)
post.view_count += 1
post.save()
Post.objects.filter(pk=1).update(view_count=F("view_count") + 1)
Indexação
Quando adicionar índices
- Campos usados frequentemente em
filter(), exclude(), order_by()
- Campos usados em
JOIN (ForeignKey já tem índice automático)
- Campos usados em lookups únicos (
get(), get_or_create())
class Post(models.Model):
title = models.CharField(max_length=255, db_index=True)
slug = models.SlugField(unique=True)
class Meta:
indexes = [
models.Index(fields=["published", "-created_at"]),
models.Index(fields=["author", "published"], name="author_published_idx"),
]
Quando NÃO adicionar índices
- Tabelas pequenas (< 1000 registros) — scan sequencial é mais rápido
- Campos com baixa cardinalidade (ex:
BooleanField com 50/50)
- Tabelas com muitas escritas e poucas leituras
Paginação
- Sempre pagine listagens — nunca retorne todos os registros
- Use
CursorPagination do DRF para datasets grandes (mais eficiente que offset)
LimitOffsetPagination fica lento com offsets grandes (>10k)
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination",
"PAGE_SIZE": 25,
}
Cache
Níveis de cache no Django
| Nível | Quando usar | Como |
|---|
| Queryset | Dados que mudam pouco | cache.get_or_set() |
| View | Páginas inteiras | @cache_page(timeout) |
| Template fragment | Partes do template | {% cache timeout key %} |
| Low-level | Qualquer dado computado | cache.set() / cache.get() |
Padrão recomendado para cache de queryset
from django.core.cache import cache
def get_published_posts():
cache_key = "published_posts"
posts = cache.get(cache_key)
if posts is None:
posts = list(Post.objects.filter(published=True).select_related("author"))
cache.set(cache_key, posts, timeout=300)
return posts
Invalidação de cache
from django.db.models.signals import post_save, post_delete
def invalidate_post_cache(sender, **kwargs):
cache.delete("published_posts")
post_save.connect(invalidate_post_cache, sender=Post)
post_delete.connect(invalidate_post_cache, sender=Post)
Ferramentas de diagnóstico
- django-debug-toolbar: mostra queries por request em desenvolvimento
- django-silk: profiling de requests e queries
- nplusone: detecta N+1 automaticamente em desenvolvimento
- EXPLAIN ANALYZE: para queries individuais via
queryset.explain()
print(Post.objects.filter(published=True).select_related("author").explain(analyze=True))
Checklist de performance para views/endpoints