性能评审清单
性能评审关注系统的响应时间、吞吐量、资源消耗。性能问题往往在上线后才暴露,修复成本高。在设计阶段就把性能考虑进去,能避免大量后期优化工作。
要点
- 性能评审关注响应时间、吞吐量、资源消耗
- 重点检查:P50/P95/P99 延迟、缓存策略、数据库优化、并发处理
- 不要过早优化,先定位瓶颈再优化
- Cloudflare Workers 有 CPU 时间限制,要注意计算密集型任务
1. 适用时机
- 新功能上线(评估对整体性能的影响)
- 现有功能重构(评估性能变化)
- 数据量增长预期大(评估当前架构能否支撑)
- 出现性能问题后(定位瓶颈,制定优化方案)
- 引入新的技术组件(评估性能特征)
2. 评审前准备
- 性能目标:P50、P95、P99 响应时间目标是多少?
- 负载估算:预期 QPS、并发用户数、数据量
- 性能基线:当前系统的性能数据是多少?
- 压测报告:有压测数据吗?瓶颈在哪里?
- 资源使用:CPU、内存、磁盘、网络的当前使用率
3. 核心检查项
3.1 响应时间
- 关键接口的响应时间目标明确(P50/P95/P99)
- 响应时间可测量(有监控、有追踪)
- 慢查询识别和优化(数据库、外部调用)
- 同步 vs 异步选择合理(长操作异步化)
- 首屏/首字节时间优化(流式响应、预加载)
Hono 示例:性能监控
import { Hono } from 'hono'
const app = new Hono()
// 性能监控中间件
app.use('*', async (c, next) => {
const start = performance.now()
await next()
const duration = performance.now() - start
// 记录性能指标
c.header('X-Response-Time', `${duration.toFixed(2)}ms`)
// 慢请求告警
if (duration > 1000) {
console.warn(`Slow request: ${c.req.method} ${c.req.path} took ${duration.toFixed(2)}ms`)
}
// 发送指标到监控系统
await c.env.KV.put(
`metrics:${c.req.path}:${Date.now()}`,
JSON.stringify({
method: c.req.method,
path: c.req.path,
duration,
status: c.res.status
}),
{ expirationTtl: 3600 }
)
})
// 批量查询优化
app.get('/api/users/with-orders', async (c) => {
// 坏的代码:N+1 查询
// const users = await db.users.findMany()
// for (const user of users) {
// user.orders = await db.orders.findByUserId(user.id) // N 次查询
// }
// 好的代码:批量查询
const users = await c.env.DB.prepare('SELECT * FROM users LIMIT 100').all()
const userIds = users.results.map(u => u.id)
const orders = await c.env.DB.prepare(
`SELECT * FROM orders WHERE user_id IN (${userIds.map(() => '?').join(',')})`
)
.bind(...userIds)
.all()
// 在内存中关联
const ordersByUser = new Map()
for (const order of orders.results) {
if (!ordersByUser.has(order.user_id)) {
ordersByUser.set(order.user_id, [])
}
ordersByUser.get(order.user_id).push(order)
}
const result = users.results.map(user => ({
...user,
orders: ordersByUser.get(user.id) || []
}))
return c.json(result)
})3.2 吞吐量
- 系统容量评估(最大 QPS、最大并发)
- 压测覆盖(峰值负载、持续负载、压力测试)
- 瓶颈识别(CPU、内存、IO、网络、数据库)
- 水平扩展能力(加机器能线性提升吗?)
- 限流策略(防止过载)
3.3 数据库性能
- 查询优化(EXPLAIN 分析、索引覆盖)
- 连接池配置合理
- 批量操作代替循环单条操作
- 避免 N+1 查询
- 大数据量分页(游标分页而不是 OFFSET)
- 读写分离(读多写少场景)
- 缓存热点数据
Hono 示例:分页优化
import { Hono } from 'hono'
const app = new Hono()
// 坏的分页:OFFSET 性能差
app.get('/api/orders', async (c) => {
const page = parseInt(c.req.query('page') || '1')
const limit = 20
const offset = (page - 1) * limit
// OFFSET 越大越慢
const orders = await c.env.DB.prepare(
'SELECT * FROM orders ORDER BY created_at DESC LIMIT ? OFFSET ?'
)
.bind(limit, offset)
.all()
return c.json(orders.results)
})
// 好的分页:游标分页
app.get('/api/orders', async (c) => {
const cursor = c.req.query('cursor')
const limit = 20
let query = 'SELECT * FROM orders'
let params: any[] = []
if (cursor) {
query += ' WHERE created_at < ?'
params.push(new Date(cursor).getTime())
}
query += ' ORDER BY created_at DESC LIMIT ?'
params.push(limit + 1) // 多查一条,判断是否有下一页
const orders = await c.env.DB.prepare(query)
.bind(...params)
.all()
const hasMore = orders.results.length > limit
const data = hasMore ? orders.results.slice(0, limit) : orders.results
const nextCursor = hasMore
? new Date(data[data.length - 1].created_at).toISOString()
: null
return c.json({
data,
pagination: {
hasMore,
nextCursor
}
})
})
// 批量插入优化
app.post('/api/orders/bulk', async (c) => {
const { orders } = await c.req.json()
// 坏的代码:循环单条插入
// for (const order of orders) {
// await db.insert(order) // N 次插入
// }
// 好的代码:批量插入
const values = orders.map((o: any) => `('${o.id}', '${o.userId}', ${o.amount})`)
const sql = `INSERT INTO orders (id, user_id, amount) VALUES ${values.join(',')}`
await c.env.DB.prepare(sql).run()
return c.json({ success: true, count: orders.length })
})3.4 缓存策略
- 缓存命中率目标(> 80% 是健康值)
- 缓存层级明确(内存 → CDN → KV → 数据库)
- 缓存键设计合理(避免冲突、避免过长)
- 缓存失效策略(TTL、主动失效、版本号)
- 缓存穿透、击穿、雪崩防护
- 缓存一致性(与数据库的同步策略)
Hono 示例:多级缓存
import { Hono } from 'hono'
const app = new Hono()
// 内存缓存(Worker 实例级别)
const memoryCache = new Map<string, { data: any; expireAt: number }>()
// 多级缓存查询
async function getCachedData(
env: Env,
key: string,
fetcher: () => Promise<any>,
ttl = 60
): Promise<any> {
// Level 1: 内存缓存
const memCached = memoryCache.get(key)
if (memCached && memCached.expireAt > Date.now()) {
return memCached.data
}
// Level 2: KV 缓存
const kvCached = await env.KV.get(key, 'json')
if (kvCached) {
// 回填内存缓存
memoryCache.set(key, {
data: kvCached,
expireAt: Date.now() + ttl * 1000
})
return kvCached
}
// Level 3: 数据库
const data = await fetcher()
// 写入缓存
await env.KV.put(key, JSON.stringify(data), { expirationTtl: ttl })
memoryCache.set(key, {
data,
expireAt: Date.now() + ttl * 1000
})
return data
}
// 使用示例
app.get('/api/users/:id', async (c) => {
const userId = c.req.param('id')
const user = await getCachedData(
c.env,
`user:${userId}`,
async () => {
return c.env.DB.prepare('SELECT * FROM users WHERE id = ?')
.bind(userId)
.first()
},
300 // 5 分钟缓存
)
if (!user) {
return c.json({ error: 'User not found' }, 404)
}
return c.json(user)
})
// 缓存失效:数据更新时主动失效
app.put('/api/users/:id', async (c) => {
const userId = c.req.param('id')
const body = await c.req.json()
await c.env.DB.prepare('UPDATE users SET name = ? WHERE id = ?')
.bind(body.name, userId)
.run()
// 主动失效缓存
await c.env.KV.delete(`user:${userId}`)
memoryCache.delete(`user:${userId}`)
return c.json({ success: true })
})
// 防止缓存穿透:空值也缓存
app.get('/api/products/:id', async (c) => {
const productId = c.req.param('id')
const cacheKey = `product:${productId}`
const cached = await c.env.KV.get(cacheKey, 'json')
if (cached !== null) {
if (cached === 'NULL') {
return c.json({ error: 'Product not found' }, 404)
}
return c.json(cached)
}
const product = await c.env.DB.prepare('SELECT * FROM products WHERE id = ?')
.bind(productId)
.first()
if (!product) {
// 缓存空值,防止穿透
await c.env.KV.put(cacheKey, 'NULL', { expirationTtl: 60 })
return c.json({ error: 'Product not found' }, 404)
}
await c.env.KV.put(cacheKey, JSON.stringify(product), { expirationTtl: 3600 })
return c.json(product)
})
// 防止缓存雪崩:TTL 加随机抖动
async function setCacheWithJitter(env: Env, key: string, data: any, baseTtl: number) {
const jitter = Math.random() * 60 // 0-60 秒随机抖动
const ttl = baseTtl + jitter
await env.KV.put(key, JSON.stringify(data), { expirationTtl: ttl })
}3.5 资源使用
- 内存使用合理(不一次性加载大文件)
- CPU 使用合理(不跑 CPU 密集型任务在 Web 进程)
- 磁盘 IO 合理(日志、临时文件清理)
- 网络带宽合理(压缩、CDN、分片)
- 连接数合理(连接池、长连接复用)
Cloudflare Workers CPU 限制
// Cloudflare Workers 有 CPU 时间限制
// 免费版:10ms CPU 时间
// 付费版:50ms CPU 时间
// 避免 CPU 密集型任务
app.post('/api/process', async (c) => {
const data = await c.req.json()
// 坏的做法:在 Worker 中处理大文件
// const result = heavyComputation(data) // 可能超时
// 好的做法:异步处理
const taskId = crypto.randomUUID()
// 发送到队列
await c.env.QUEUE.send({ taskId, data })
// 立即返回任务 ID
return c.json({ taskId, status: 'processing' }, 202)
})
// 客户端轮询结果
app.get('/api/tasks/:id', async (c) => {
const taskId = c.req.param('id')
const task = await c.env.KV.get(`task:${taskId}`, 'json')
if (!task) {
return c.json({ error: 'Task not found' }, 404)
}
return c.json(task)
})3.6 并发与异步
- 并发处理合理(线程池、协程池配置)
- 异步任务处理(队列、后台任务)
- 锁粒度合理(避免大范围锁、死锁)
- 竞态条件处理(乐观锁、CAS)
- 超时设置合理(不无限等待)
3.7 前端性能(如涉及)
- 资源加载优化(懒加载、预加载、代码分割)
- 渲染性能(虚拟列表、防抖节流)
- 包体积优化(tree-shaking、压缩)
- 缓存策略(静态资源长缓存、HTML 短缓存)
- CDN 使用(静态资源、API 响应)
3.8 监控与告警
- 性能指标监控(响应时间、QPS、错误率)
- 资源使用监控(CPU、内存、磁盘、网络)
- 慢查询监控(数据库、外部调用)
- 性能退化告警(响应时间突然变长)
- 容量预警(资源使用率超过阈值)
3.9 优化空间评估
- 当前性能是否满足需求?不满足差多少?
- 优化优先级(先优化瓶颈最大的环节)
- 优化成本收益(投入多少人力,提升多少性能)
- 长期优化路径(架构调整、技术升级)
4. 评审会上容易漏的点
- P99 延迟:问「最慢的 1% 请求需要多久?为什么?」
- 数据增长:问「数据量翻 10 倍后,这个查询还能这么快吗?」
- 缓存失效:问「缓存失效的瞬间会发生什么?」
- 并发场景:问「100 个用户同时做这个操作,系统会怎样?」
- 资源上限:问「内存/磁盘/网络用完了会怎样?」
5. 通过标准
- 性能目标明确且可达
- 压测数据支撑(有真实数据,不是拍脑袋)
- 瓶颈识别清楚,有优化方案
- 缓存策略合理
- 监控告警覆盖
- 资源使用在安全范围内
6. 反模式
- 过早优化:还没定位瓶颈就盲目优化
- 只看平均:只看平均响应时间,不看 P95/P99
- 忽视数据库:应用层优化再好,数据库慢查询也白搭
- 不压测:凭感觉判断性能,没有数据支撑
- 缓存滥用:什么都缓存,导致一致性问题
7. 维护建议
性能评审清单需要随系统演进更新:
- 数据规模增长后,调整查询性能和缓存相关要求
- 引入新技术组件时,补充对应性能检查项
- 出现性能事故后,把根因对应的检查项加进来
- 每半年复审一次,删除过时的检查项