perf(core): Wave 2 part1 — 复合索引(只生成不apply) + 消息中心 N+1 治理

索引(只 makemigrations 看 SQL,远程 MySQL apply 留用户低峰 / rule 3):
- Asset (team,category,-created_at)+(team,asset_type)、CreditLedger (team,-created_at)+
  (team,ledger_type,created_at)、Project (team,-updated_at)、AITask (team,-created_at)、
  Product (team,-created_at);迁移 assets0005/billing0002/projects0004/ai0008/products0002

消息中心 N+1(ops/views.py):
- type_counts 原 6 次 count(每请求)→ 一次 aggregate(Count(filter=...)) 条件聚合
- ensure_team_notifications 项目花费原逐项目 aggregate(N+1)→ 一次 values('project').annotate(Sum)

验证: apps.ops 测试 3/3 OK;全量 6fail+1err 均为预存 image provider 路由失败(stash 跑 baseline
  复现)= 零新增回归;索引迁移在 sqlite 测试库成功 apply

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 04:18:30 +08:00
co-authored by Claude Opus 4.8
parent 42c3c046ea
commit 99a442e883
12 changed files with 182 additions and 14 deletions
@@ -0,0 +1,25 @@
# Generated by Django 5.1.15 on 2026-06-18 20:13
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_loginsession_userpreference'),
('ai', '0008_aitask_ai_aitask_team_id_570138_idx'),
('assets', '0004_asset_review_db_defaults'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddIndex(
model_name='asset',
index=models.Index(fields=['team', 'category', '-created_at'], name='assets_asse_team_id_4b1b57_idx'),
),
migrations.AddIndex(
model_name='asset',
index=models.Index(fields=['team', 'asset_type'], name='assets_asse_team_id_51a9e4_idx'),
),
]
+7
View File
@@ -45,6 +45,13 @@ class Asset(TeamOwnedModel):
review_remote_id = models.CharField(max_length=128, blank=True, db_default="")
review_error = models.TextField(blank=True, null=True, default="") # TEXT 不能有 DB 默认值(MySQL 1101),改用可空容忍「漏带字段」的插入
class Meta:
indexes = [
# 资产库按团队 + 分类倒序时间翻页(products 详情素材趴 / 资产库)
models.Index(fields=["team", "category", "-created_at"]),
models.Index(fields=["team", "asset_type"]),
]
def __str__(self) -> str:
return self.name