[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-71c9f968-bd20-4b20-b11c-a5038a58ffd3":3,"$ftUOYFFV2BpEohwqWp6pw-tnU6QvYOhiziBorFUjjofw":42},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":33},"71c9f968-bd20-4b20-b11c-a5038a58ffd3","django-perf-review","Django性能代码审查。用于被要求“审查Django性能”、“查找N+1查询”、“优化Django”、“检查查询集性能”、“数据库性能”、“Django ORM问题”或审计Django代码以查找性能问题。","cat_coding_backend","mod_coding","sickn33,coding","---\nname: django-perf-review\ndescription: Django performance code review. Use when asked to \"review Django performance\", \"find N+1 queries\", \"optimize Django\", \"check queryset performance\", \"database performance\", \"Django ORM issues\", or audit Django code for performance problems.\nallowed-tools: Read, Grep, Glob, Bash, Task\nlicense: LICENSE\nrisk: unknown\nsource: community\n---\n\n# Django Performance Review\n\nReview Django code for **validated** performance issues. Research the codebase to confirm issues before reporting. Report only what you can prove.\n\n## When to Use\n- You need a Django performance review focused on verified ORM and query issues.\n- The code likely has N+1 queries, unbounded querysets, missing indexes, or other database-driven bottlenecks.\n- You want only provable performance findings, not speculative optimization advice.\n\n## Review Approach\n\n1. **Research first** - Trace data flow, check for existing optimizations, verify data volume\n2. **Validate before reporting** - Pattern matching is not validation\n3. **Zero findings is acceptable** - Don't manufacture issues to appear thorough\n4. **Severity must match impact** - If you catch yourself writing \"minor\" in a CRITICAL finding, it's not critical. Downgrade or skip it.\n\n## Impact Categories\n\nIssues are organized by impact. Focus on CRITICAL and HIGH - these cause real problems at scale.\n\n| Priority | Category | Impact |\n|----------|----------|--------|\n| 1 | N+1 Queries | **CRITICAL** - Multiplies with data, causes timeouts |\n| 2 | Unbounded Querysets | **CRITICAL** - Memory exhaustion, OOM kills |\n| 3 | Missing Indexes | **HIGH** - Full table scans on large tables |\n| 4 | Write Loops | **HIGH** - Lock contention, slow requests |\n| 5 | Inefficient Patterns | **LOW** - Rarely worth reporting |\n\n---\n\n## Priority 1: N+1 Queries (CRITICAL)\n\n**Impact:** Each N+1 adds `O(n)` database round trips. 100 rows = 100 extra queries. 10,000 rows = timeout.\n\n### Rule: Prefetch related data accessed in loops\n\nValidate by tracing: View → Queryset → Template\u002FSerializer → Loop access\n\n```python\n# PROBLEM: N+1 - each iteration queries profile\ndef user_list(request):\n    users = User.objects.all()\n    return render(request, 'users.html', {'users': users})\n\n# Template:\n# {% for user in users %}\n#     {{ user.profile.bio }}  ← triggers query per user\n# {% endfor %}\n\n# SOLUTION: Prefetch in view\ndef user_list(request):\n    users = User.objects.select_related('profile')\n    return render(request, 'users.html', {'users': users})\n```\n\n### Rule: Prefetch in serializers, not just views\n\nDRF serializers accessing related fields cause N+1 if queryset isn't optimized.\n\n```python\n# PROBLEM: SerializerMethodField queries per object\nclass UserSerializer(serializers.ModelSerializer):\n    order_count = serializers.SerializerMethodField()\n\n    def get_order_count(self, obj):\n        return obj.orders.count()  # ← query per user\n\n# SOLUTION: Annotate in viewset, access in serializer\nclass UserViewSet(viewsets.ModelViewSet):\n    def get_queryset(self):\n        return User.objects.annotate(order_count=Count('orders'))\n\nclass UserSerializer(serializers.ModelSerializer):\n    order_count = serializers.IntegerField(read_only=True)\n```\n\n### Rule: Model properties that query are dangerous in loops\n\n```python\n# PROBLEM: Property triggers query when accessed\nclass User(models.Model):\n    @property\n    def recent_orders(self):\n        return self.orders.filter(created__gte=last_week)[:5]\n\n# Used in template loop = N+1\n\n# SOLUTION: Use Prefetch with custom queryset, or annotate\n```\n\n### Validation Checklist for N+1\n- [ ] Traced data flow from view to template\u002Fserializer\n- [ ] Confirmed related field is accessed inside a loop\n- [ ] Searched codebase for existing select_related\u002Fprefetch_related\n- [ ] Verified table has significant row count (1000+)\n- [ ] Confirmed this is a hot path (not admin, not rare action)\n\n---\n\n## Priority 2: Unbounded Querysets (CRITICAL)\n\n**Impact:** Loading entire tables exhausts memory. Large tables cause OOM kills and worker restarts.\n\n### Rule: Always paginate list endpoints\n\n```python\n# PROBLEM: No pagination - loads all rows\nclass UserListView(ListView):\n    model = User\n    template_name = 'users.html'\n\n# SOLUTION: Add pagination\nclass UserListView(ListView):\n    model = User\n    template_name = 'users.html'\n    paginate_by = 25\n```\n\n### Rule: Use iterator() for large batch processing\n\n```python\n# PROBLEM: Loads all objects into memory at once\nfor user in User.objects.all():\n    process(user)\n\n# SOLUTION: Stream with iterator()\nfor user in User.objects.iterator(chunk_size=1000):\n    process(user)\n```\n\n### Rule: Never call list() on unbounded querysets\n\n```python\n# PROBLEM: Forces full evaluation into memory\nall_users = list(User.objects.all())\n\n# SOLUTION: Keep as queryset, slice if needed\nusers = User.objects.all()[:100]\n```\n\n### Validation Checklist for Unbounded Querysets\n- [ ] Table is large (10k+ rows) or will grow unbounded\n- [ ] No pagination class, paginate_by, or slicing\n- [ ] This runs on user-facing request (not background job with chunking)\n\n---\n\n## Priority 3: Missing Indexes (HIGH)\n\n**Impact:** Full table scans. Negligible on small tables, catastrophic on large ones.\n\n### Rule: Index fields used in WHERE clauses on large tables\n\n```python\n# PROBLEM: Filtering on unindexed field\n# User.objects.filter(email=email)  # full scan if no index\n\nclass User(models.Model):\n    email = models.EmailField()  # ← no db_index\n\n# SOLUTION: Add index\nclass User(models.Model):\n    email = models.EmailField(db_index=True)\n```\n\n### Rule: Index fields used in ORDER BY on large tables\n\n```python\n# PROBLEM: Sorting requires full scan without index\nOrder.objects.order_by('-created')\n\n# SOLUTION: Index the sort field\nclass Order(models.Model):\n    created = models.DateTimeField(db_index=True)\n```\n\n### Rule: Use composite indexes for common query patterns\n\n```python\nclass Order(models.Model):\n    user = models.ForeignKey(User)\n    status = models.CharField(max_length=20)\n    created = models.DateTimeField()\n\n    class Meta:\n        indexes = [\n            models.Index(fields=['user', 'status']),  # for filter(user=x, status=y)\n            models.Index(fields=['status', '-created']),  # for filter(status=x).order_by('-created')\n        ]\n```\n\n### Validation Checklist for Missing Indexes\n- [ ] Table has 10k+ rows\n- [ ] Field is used in filter() or order_by() on hot path\n- [ ] Checked model - no db_index=True or Meta.indexes entry\n- [ ] Not a foreign key (already indexed automatically)\n\n---\n\n## Priority 4: Write Loops (HIGH)\n\n**Impact:** N database writes instead of 1. Lock contention. Slow requests.\n\n### Rule: Use bulk_create instead of create() in loops\n\n```python\n# PROBLEM: N inserts, N round trips\nfor item in items:\n    Model.objects.create(name=item['name'])\n\n# SOLUTION: Single bulk insert\nModel.objects.bulk_create([\n    Model(name=item['name']) for item in items\n])\n```\n\n### Rule: Use update() or bulk_update instead of save() in loops\n\n```python\n# PROBLEM: N updates\nfor obj in queryset:\n    obj.status = 'done'\n    obj.save()\n\n# SOLUTION A: Single UPDATE statement (same value for all)\nqueryset.update(status='done')\n\n# SOLUTION B: bulk_update (different values)\nfor obj in objects:\n    obj.status = compute_status(obj)\nModel.objects.bulk_update(objects, ['status'], batch_size=500)\n```\n\n### Rule: Use delete() on queryset, not in loops\n\n```python\n# PROBLEM: N deletes\nfor obj in queryset:\n    obj.delete()\n\n# SOLUTION: Single DELETE\nqueryset.delete()\n```\n\n### Validation Checklist for Write Loops\n- [ ] Loop iterates over 100+ items (or unbounded)\n- [ ] Each iteration calls create(), save(), or delete()\n- [ ] This runs on user-facing request (not one-time migration script)\n\n---\n\n## Priority 5: Inefficient Patterns (LOW)\n\n**Rarely worth reporting.** Include only as minor notes if you're already reporting real issues.\n\n### Pattern: count() vs exists()\n\n```python\n# Slightly suboptimal\nif queryset.count() > 0:\n    do_thing()\n\n# Marginally better\nif queryset.exists():\n    do_thing()\n```\n\n**Usually skip** - difference is \u003C1ms in most cases.\n\n### Pattern: len(queryset) vs count()\n\n```python\n# Fetches all rows to count\nif len(queryset) > 0:  # bad if queryset not yet evaluated\n\n# Single COUNT query\nif queryset.count() > 0:\n```\n\n**Only flag** if queryset is large and not already evaluated.\n\n### Pattern: get() in small loops\n\n```python\n# N queries, but if N is small (\u003C 20), often fine\nfor id in ids:\n    obj = Model.objects.get(id=id)\n```\n\n**Only flag** if loop is large or this is in a very hot path.\n\n---\n\n## Validation Requirements\n\nBefore reporting ANY issue:\n\n1. **Trace the data flow** - Follow queryset from creation to consumption\n2. **Search for existing optimizations** - Grep for select_related, prefetch_related, pagination\n3. **Verify data volume** - Check if table is actually large\n4. **Confirm hot path** - Trace call sites, verify this runs frequently\n5. **Rule out mitigations** - Check for caching, rate limiting\n\n**If you cannot validate all steps, do not report.**\n\n---\n\n## Output Format\n\n```markdown\n## Django Performance Review: [File\u002FComponent Name]\n\n### Summary\nValidated issues: X (Y Critical, Z High)\n\n### Findings\n\n#### [PERF-001] N+1 Query in UserListView (CRITICAL)\n**Location:** `views.py:45`\n\n**Issue:** Related field `profile` accessed in template loop without prefetch.\n\n**Validation:**\n- Traced: UserListView → users queryset → user_list.html → `{{ user.profile.bio }}` in loop\n- Searched codebase: no select_related('profile') found\n- User table: 50k+ rows (verified in admin)\n- Hot path: linked from homepage navigation\n\n**Evidence:**\n```python\ndef get_queryset(self):\n    return User.objects.filter(active=True)  # no select_related\n```\n\n**Fix:**\n```python\ndef get_queryset(self):\n    return User.objects.filter(active=True).select_related('profile')\n```\n```\n\nIf no issues found: \"No performance issues identified after reviewing [files] and validating [what you checked].\"\n\n**Before submitting, sanity check each finding:**\n- Does the severity match the actual impact? (\"Minor inefficiency\" ≠ CRITICAL)\n- Is this a real performance issue or just a style preference?\n- Would fixing this measurably improve performance?\n\nIf the answer to any is \"no\" - remove the finding.\n\n---\n\n## What NOT to Report\n\n- Test files\n- Admin-only views\n- Management commands\n- Migration files\n- One-time scripts\n- Code behind disabled feature flags\n- Tables with \u003C1000 rows that won't grow\n- Patterns in cold paths (rarely executed code)\n- Micro-optimizations (exists vs count, only\u002Fdefer without evidence)\n\n### False Positives to Avoid\n\n**Queryset variable assignment is not an issue:**\n```python\n# This is FINE - no performance difference\nprojects_qs = Project.objects.filter(org=org)\nprojects = list(projects_qs)\n\n# vs this - identical performance\nprojects = list(Project.objects.filter(org=org))\n```\nQuerysets are lazy. Assigning to a variable doesn't execute anything.\n\n**Single query patterns are not N+1:**\n```python\n# This is ONE query, not N+1\nprojects = list(Project.objects.filter(org=org))\n```\nN+1 requires a loop that triggers additional queries. A single `list()` call is fine.\n\n**Missing select_related on single object fetch is not N+1:**\n```python\n# This is 2 queries, not N+1 - report as LOW at most\nstate = AutofixState.objects.filter(pr_id=pr_id).first()\nproject_id = state.request.project_id  # second query\n```\nN+1 requires a loop. A single object doing 2 queries instead of 1 can be reported as LOW if relevant, but never as CRITICAL\u002FHIGH.\n\n**Style preferences are not performance issues:**\nIf your only suggestion is \"combine these two lines\" or \"rename this variable\" - that's style, not performance. Don't report it.\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,247,969,"2026-05-16 13:15:35",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":25,"skillCount":32,"createdAt":26},"后端开发","backend","mdi-server","API、数据库、服务端架构",296,[34],{"id":35,"skillId":4,"version":36,"fileName":37,"fileSize":38,"filePath":39,"fileHash":40,"manifest":41,"createdAt":19},"ec76c707-ca4b-4c63-b720-41b086798d77","1.0.0","django-perf-review.zip",4688,"uploads\u002Fskills\u002F71c9f968-bd20-4b20-b11c-a5038a58ffd3\u002Fdjango-perf-review.zip","0441bde164b4a6288e8974b3c60ab4ef448df8f9fd4b80aa3b7c71ad275a57fd","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":12073}]",{"code":43,"message":44,"data":45},200,"success",{"items":46,"stats":47,"page":50},[],{"averageRating":48,"totalRatings":48,"ratingCounts":49},0,[48,48,48,48,48],{"limit":51,"offset":48,"hasMore":52,"nextOffset":51,"ratedOnly":16},15,false]