- 新增 UI 还原对比报告 / 性能审计报告(2026-06-19,14 块页面 + 6 块性能,逐项 file:line) - auth-screen: 删除登录成功后 ~1.5s 人为进度动画延迟,凭证验过即进 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 KiB
AirShelf · 性能审计报告
范围: 全量扫描 —— 前端(数据加载/水合、列表渲染、pipeline 重渲染、全站轮询)+ 后端(ORM N+1、端点耗时/payload、数据库索引) 背景: 用户反馈刷新/进页面拉数据慢、不够丝滑;张业昌已优化过一波,仍有体感卡顿 日期: 2026-06-19 方法: 6 路并行审计,每块两端源码核查到 file:line 严重度: P0 = 明显卡顿/秒级阻塞 · P1 = 可感知延迟 · P2 = 优化空间
〇、先说结论:"刷新慢/不丝滑"的真正元凶
按"改动成本 vs 体感收益"排序。前两条是一行/小改就能立竿见影的,强烈建议先做。
| # | 根因 | 类型 | 现状 | 一句话修复 | 收益 |
|---|---|---|---|---|---|
| 🔴 1 | 线上 DB 连接不复用 | 后端配置 | production.py 缺 CONN_MAX_AGE,每请求重连远程 MySQL ~1.6s;并发 N 接口=N×1.6s。development 有这条优化,线上没搬 |
production.py 加 CONN_MAX_AGE=300+CONN_HEALTH_CHECKS |
刷新整页快几十秒→秒级 |
| 🔴 2 | 登录假动画延迟 1.5s | 前端 | 凭证验过后还硬演 verified+entering 两段共 ~1.5s | 已修(见本报告末) | 登录即时进 |
| 🔴 3 | 视频生成期 5s 全量回拉项目详情 | 前后端 | 有视频在跑时每 5s 调 api.project(),跑最重的 12-prefetch 序列化器、回 ~26KB,整页每 5 秒抖一下 |
轮询改用轻量 poll-video-segment 单段状态 |
视频阶段不再整页抖、payload 砍 99% |
| 🔴 4 | 消息中心首屏同步 N+1 爆炸 | 后端 | 进消息面板时 list() 里同步循环 aggregate+get_or_create,单次可达 500~1000+ 条 SQL |
批量聚合 + bulk_create + 改 Celery 异步 | 打开消息面板秒回 |
| 🔴 5 | Asset 大表零索引 | 后端索引 | 全站最大增长表,资产库列表/计数/facets 全表扫 + filesort | 加 (team,category,-created_at) 等复合索引 |
资产多时数量级提速 |
| 🔴 6 | pipeline 3100 行巨页不拆不 memo | 前端 | 任一 state 变(打字/轮询/hover)reconcile 整棵树 | 5 个 stage 拆 memo 子组件 + 派生数据 useMemo | 输入/拖动恢复跟手 |
| 🟠 7 | 切走再回必重拉、无缓存 | 前端 | 换页即卸载,每次进页面从零拉、白骨架闪烁 | 加内存缓存 + stale-while-revalidate | 二次进页 0 等待 |
| 🟠 8 | 大页一次拉 200 条 + img 漏 lazy | 前端 | 商品详情/图片工具 pageSize:200,任务中心缩图无 loading=lazy |
改服务端分页 + 补 lazy | 首屏字节/解码降一个量级 |
一、前端 · 数据加载与水合(7/10)
水合链路已认真优化过:骨架先出、
loadData并行 Promise.all、不阻塞首屏、liteRefresh、懒加载——核心卡顿点基本消除。剩下的是缓存与"小操作全量重拉"。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P1 | 各路由 + App.tsx renderPage switch |
切走再回每次都重拉:换页即卸载旧组件,reloadFlag 归零,资产库/消息/团队/消费/图片工具/商品详情/工作台进去都从零拉,无任何缓存 |
反复横跳每次白骨架闪烁,不丝滑 | 模块级内存缓存或 SWR:先渲染上次结果,再后台 revalidate |
| P1 | App.tsx:387 action() |
几乎所有变更都触发全量 loadData()(并发重拉 products+projects+billing+models+badge 五个);只有 pipeline 改/加/删分镜传了 liteRefresh |
改商品/传图/改资料后顶栏侧栏整体抖一下,且并发拉无关数据 | 给不动列表的操作普遍补 liteRefresh,或让调用方只刷相关切片 |
| P1 | App.tsx:388 action() |
每次 action 无条件 refreshProjectDetail(),即便不在 pipeline、操作与项目无关(改头像/充值/建商品都白拉 26KB 项目详情) |
非管线页做操作时白发一个大请求 | 仅 page==="pipeline" 或操作确属项目相关时才刷 |
| P2 | ai-tools.tsx:94/products.tsx:551/:568 |
pageSize:200 一次拉大页再前端筛/分页 |
资产多的团队首屏偏慢 | 改服务端分页 + 触底加载 |
| P2 | 站内点进管线 App.tsx:759-786 |
详情未到时整页 loading,未与导航预取重叠 | 从项目列表点进管线有一次明显等待 | openPipeline 时即并行预取 api.project(id) |
✅ 已核实无问题:loadData 并行且不阻塞首屏、骨架态完整、搜索去抖到位、防连点到位、无人为延迟卡流程(除已修的登录)。
二、前端 · 列表与图片渲染(6.5/10)
分页/懒加载/去抖基础都对,但几处"大页一次拉 + 一次性渲染"和漏 lazy,会在素材多的真实账号上突然变卡。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P0 | 商品详情 products.tsx:551 |
进详情一次拉该商品最多 200 条素材,前端再 filter+slice 只显示 12,200 条全进 state | 素材多的商品点进去明显"转一下" | 服务端按页拉(page+pageSize=12),"加载更多"走下一页 |
| P0 | 图片工具任务中心 ai-tools.tsx:314,349 |
结果缩图 <img> 漏 loading="lazy"(同文件别处都加了)+ :94 一次拉 200 张,首屏并发几十张原图 |
进"图片生成"页一批图齐刷、带宽抖动 | 两处补 loading="lazy",源数据改分页 |
| P0 | 商品库/项目页 App.tsx:141-142+api.ts:175,204 |
api.products()/projects() 只取后端第 1 页 20 条,却被当"全量"做客户端搜索/筛选/分页 → 第 21 个起根本不在内存 |
搜不到/筛不到第 20 名以后的数据,用户误判"卡了没加载" | products/projects 改服务端分页(像 library/messages 那样) |
| P1 | 图片工具模特网格 ai-tools.tsx:568 |
一次拉 200 个模特进 state,点"全部模特"渲染全部缩图 | 模特库大时工作台首开+展开卡 | 默认拉前若干,"全部"走分页 |
| P1 | 商品详情视频 tab products.tsx:643-646 |
videoProjects 不分页不截断,一次渲染全部视频卡(assets tab 有 limit,videos 没有) |
项目多的商品切到视频 tab 一次铺满 | videos tab 也加 limit/分页 |
| P1 | 向导商品选择器 projects.tsx:278-280 |
页码用 Array.from({length:totalPages}) 全量展开,没用 pageWindow 折叠 |
商品多时页码一长条 | 复用 pageWindow() 折叠 |
| P2 | 商品库 products.tsx:124-134 |
搜索纯客户端但每键触发整列表重算;ProductCard 未 memo,map 内联闭包 |
商品多时逐字搜索略顿 | ProductCard 包 memo,filtered 用 useMemo |
| P2 | 列表行未 memo / key 用 index | projects.tsx:588/account.tsx:331,347 内联闭包无 memo;products.tsx:435,819 key=index |
当前每页 10 条无碍,数据增长后退化 | 列表项抽组件 memo,key 用稳定 id |
✅ 正向:六个页面 loading 都用骨架不白屏;messages/library/account 已是真服务端分页+去抖,messages 还做了滚动无限加载+防竞态。
三、前端 · pipeline 重渲染与轮询(5/10)
轮询清理/动画做得不错,但单个 3100 行巨页 + 5s 全量回拉让整页每 5 秒抖,后台 tab 也照拉不停。这是 pipeline"不丝滑"的核心。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P0 | App.tsx:320-334+api.ts:207 |
视频 5s 轮询每轮 api.project() 拉全量 ~26KB再 setProjectDetail,无脏检查,即便没变也 set → 整页重渲染 |
有视频在跑时整页每 5 秒"刷一下",滚动/输入发顿 | 后端加轻量 video-status 端点;或 set 前对比版本号/JSON 跳过 |
| P0 | pipeline.tsx:445(整组件)+503-585 |
单个 3100 行函数持 60+ useState,5 阶段全在一棵树;byId/scripts/shots/groups 每次渲染重算(memo 远不够),组件无 memo 无拆分 |
任一 state 变(打字/轮询/hover)reconcile 整棵巨树,输入延迟 | 5 阶段拆 memo 子组件只渲当前 stage;派生数据包 useMemo |
| P1 | 全站轮询无 visibilitychange |
5s/8s/4s 轮询在隐藏 tab/切后台时照跑(全局 0 命中 document.hidden) | 切走后仍持续打接口,耗电耗流量,服务端空转 | poll tick 起始加 if(document.hidden)return |
| P1 | pipeline.tsx:2076-2095 |
stage2 pendingAssets 4s 轮询空闲也永不停,list.length==0 也照 setTimeout |
停在资产页啥也不干也每 4s 打接口 | 连续 N 轮空就停,有新提交再启 |
| P2 | pipeline.tsx:84-89 getAssetBlobUrl |
createObjectURL 进 module Map 缓存后全程不 revoke,跨项目累积 |
长会话内存缓慢上涨 | 切/卸载项目时 revoke + 清 Map |
✅ 已核实无问题:ProgressTimeline 的 1s interval 作用域小且 done 即停;两处 rAF 正确 gate+cleanup;5s/8s/120ms 轮询都在卸载时清理且按阶段 gate;CSS infinite 动画都是条件出现的 spinner/shimmer;无人为卡流程的 setTimeout。
四、后端 · ORM N+1 与序列化器(核心路径已治理,集中 2 个热点)
详情/列表核心路径已被认真优化(prefetch、defer 大 JSON 列、annotate 计数、轻量列表序列化器)。真正的放大点集中在消息中心首屏 + 写操作回吐全量。
| 严重度 | 位置 | 问题(估算多发 SQL) | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P0 | apps/ops/views.py:69-235 ensure_team_notifications(在 list() 同步触发) |
首屏循环内:5 项目各 credit_ledgers.aggregate() + 拉最多 500 条 charge 流水逐条 get_or_create。单次首屏 500~1000+ 条往返,远程库几秒~几十秒 |
第一次开消息面板/换团队整页长转圈 | 项目花费 values('project').annotate(Sum) 批量;charge 通知批量查 dedupe_key + bulk_create;首屏走 Celery 异步 |
| P1 | apps/ops/views.py:285-292 type_counts |
每次列表请求额外 6 条 .count()(all/unread/task/team/billing/system) |
收件箱翻页每次多 6 个往返 | 一次 values('notification_type').annotate(Count)+单 unread count = 2 条 |
| P1 | apps/projects/views.py 写 action 一律回 ProjectSerializer(project).data(:491/544/681/781 等) |
每个采用版本/上传/存草稿按钮都回吐整棵项目树(几十~上百行),个别 action 未走 prefetch 还会 N+1 | 每次写操作后界面可感卡顿 + 回包巨大 | 只回受影响子对象(已有 VideoSegmentVersionSerializer 等);必须回全量则走带 prefetch 的重取 |
| P2 | apps/projects/serializers.py:90-100 BaseAssetGroupSerializer |
线索核实:列表/详情不 N+1(queryset 已 prefetch adopted_asset/candidate/files)。仅 adopt_base_asset 单独序列化未预取的 group 时每组 +2~3 条 |
采用基础资产后单次轻微多查 | 单对象序列化前补 select_related/prefetch |
| P2 | apps/assets/views.py:98-118 summary/facets |
资产库徽标 6 条 count、facets 多条 distinct(固定不放大) | 进资产库一组并发 count | 可合成一条 aggregate(Count(Case(When))) |
✅ 已核实无问题:项目列表用 ProjectListSerializer+annotate 无 N+1;详情/商品/AITask/资产列表均已 select_related/prefetch_related + defer 大 JSON;billing/team_members 已批量化。
五、后端 · 端点耗时与 payload(7/10)
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P0 | settings/production.py(缺 CONN_MAX_AGE)vs development.py:10-13 |
线上 MySQL 连接未复用,每请求新建连接 ~1.6s 握手。开发环境设了 300s 复用并注明"登录并发 12 接口=卡几十秒",这条优化只在 development 生效 | 线上每接口多 1.6s,一进页面并发 N 请求=N×握手 | production.py 加 CONN_MAX_AGE=300+CONN_HEALTH_CHECKS=True(提到 base.py 更稳) |
| P0 | App.tsx:324,331+pipeline.tsx:1876→projects/views.py:118 |
任一视频段 running 时每 5s 调 api.project,触发 12-prefetch 最重序列化器,把全部版本/分镜/资产/timeline 一次塞响应 |
视频阶段持续拉巨 payload + 整页重渲 | 轮询改用已有轻量 poll-video-segment(views.py:546)只回单段状态;或详情加 ETag→304 |
| P1 | projects/views.py:278+assets/review.py:111,前端 pipeline.tsx:816(8s) |
poll-reviews 在请求线程里同步循环调火山外部 HTTP(每个审核中资产一次),前端每 8s 打一次 |
N 个审核资产时单次请求阻塞=N×火山往返 | 火山审核轮询挪 Celery 后台,端点只读 DB review_status |
| P1 | apps/ai/views.py:68 AITaskViewSet |
未设显式 ordering,分页可能重/漏行 |
任务多时前端反复拉 | 加 ordering=["-created_at"] |
| P2 | apps/ops/views.py:276 list |
每次额外 6 条 count(同上 N+1 P1) | 无高频轮询,影响有限 | 合并 count |
| P2 | apps/billing/views.py:182 trend |
"按阶段分布"用 Python 循环累加本月全部流水,非 DB 聚合 | 流水多时账户页加载慢 | 改 values('task__task_type').annotate(Sum) |
✅ 已核实无问题:全局分页健全(PAGE_SIZE=20,无全量返回);assets/AITask/billing 已 defer 3MB+ JSON 大列(注释"40 条要 30s+"已修);poll-storyboard 后台线程不阻塞;通知生成已 Celery+60s 缓存。
六、后端 · 数据库索引(7/10)
基表
teamFK 自带单列索引,纯filter(team=)不慢。真正缺的全是"team + 第二过滤列/时间排序"的复合索引。远程 MySQL 上数据量上来后,缺索引的过滤/排序会明显变慢。
| 严重度 | 表/字段 | 问题 | 体感影响 | 加什么索引 |
|---|---|---|---|---|
| P0 | Asset (team,category,-created_at) 等 |
assets/models.py:6 整表零自定义索引,资产库每 tab filter(team,category).order_by(-created_at) + 6 个 count + facets distinct。Asset 是全站最大增长表 |
资产库刷新/切 tab/徽标计数,大表全表扫+filesort 秒级卡 | Index(["team","category","-created_at"]) + (team,asset_type) + 视需要 (team,source) |
| P0 | Asset (team,-created_at) |
默认无筛选排序也要 team 内取全部再 filesort | 默认视图随资产数线性变慢 | Index(["team","-created_at"])(或被上条复合覆盖) |
| P1 | CreditLedger (team,-created_at) |
流水分页 filter(team).order_by(-created_at),现有索引不覆盖。Ledger 高频写入 |
账单流水翻页随增长变慢 | Index(["team","-created_at"]) |
| P1 | CreditLedger created_at 范围 |
消费趋势 filter(team,ledger_type=CHARGE,created_at__date__gte)+TruncDate 分组无法走索引 |
账户消费分析月度数据多时慢 | (team,ledger_type) 扩成 (team,ledger_type,created_at) |
| P1 | AITask (team,-created_at) |
无显式 ordering + 无时间序索引 | 任务列表分页不稳+filesort | Index(["team","-created_at"]) + ViewSet ordering |
| P1 | Project (team,-updated_at) |
列表 filter(team).order_by(-updated_at),索引不含 updated_at |
项目列表/侧栏随项目数变慢 | Index(["team","-updated_at"]) |
| P1 | Product (team,-created_at) |
列表无显式 ordering + 时间序索引 | 商品列表分页随商品数变慢 | Index(["team","-created_at"]) + 默认 ordering |
| P2 | Asset metadata__product_id |
按商品过滤走 JSON 提取 + 三路 Q 并集 distinct,无法走普通索引 | 商品详情"该商品资产"大表下慢 | MySQL 函数/生成列索引,或冗余真实 product_id 列 + (team,product_id) |
⚠️ 远程 MySQL 大表
ALTER TABLE ADD INDEX会锁表,建议低峰执行或用 online DDL。Notification 表(ops/models.py:52)索引已做得很好,可作其它表范本。
七、已修复
- 登录假动画延迟(🔴2) —— auth-screen.tsx 已删除
api.login()成功后的 verified/entering 两段共 ~1.5s 人为wait,以及 checking 阶段的最小垫时。现在凭证验过即进,"进入"态由真实的onAuthed(身份+数据水合)驱动,不再演动画。
八、建议执行顺序(按 ROI)
第一波 · 一行/小改,立竿见影(强烈建议先做)
- 🔴1 production.py 加
CONN_MAX_AGE—— 消掉线上每请求 1.6s 握手,"刷新慢"最大单点 - 🔴3 视频 5s 轮询改轻量端点 —— 别再每 5s 拉全量项目详情
- 🔴8 两处
<img>补loading="lazy"+ 商品详情/图片工具改服务端分页 - 隐藏 tab 暂停轮询(poll 加 document.hidden 守卫)
第二波 · 后端查询治理
- 🔴4 消息中心首屏 N+1 —— 批量聚合 + bulk_create + Celery 异步
- 🔴5 Asset/Ledger/Project/AITask 加复合索引(低峰执行 DDL)
- poll-reviews 火山同步 HTTP 挪 Celery
- 写 action 不再回吐全量 ProjectSerializer
第三波 · 前端体验
- 🔴6 pipeline 巨页拆 memo 子组件(工作量大,但根治 pipeline 不丝滑)
- 🟠7 切走再回加内存缓存 + SWR
- action() 普遍补 liteRefresh + 条件化 refreshProjectDetail
九、客观说明
- 张业昌确实优化过一波且方向对:骨架先出、并行水合、defer 大 JSON 列、列表轻量序列化器、prefetch、防竞态、去抖——这些都到位,不是没做。
- 剩下的卡顿高度集中:① 一条线上配置漏搬(CONN_MAX_AGE)② 视频轮询拉全量 ③ 消息中心首屏 N+1 ④ Asset 缺索引 ⑤ pipeline 巨页。修掉前 4 个(都不算大改),"刷新慢"基本能解决;pipeline 巨页拆分是更大的工程,可单独排期。
- 本报告每条都带 file:line,可直接当修复 checklist 交给张业昌认领。