性能调优
AuraBoot 的性能工作有固定顺序。先调影响每个请求的层——JVM、连接池、PostgreSQL——再调子系统专属旋钮。本页记录真正的杠杆、默认值,以及指出哪根错了的症状。它不追求穷举,它追求让你不调错层。
如果你在排查具体故障而不是建立生产基线,先读 可观测性 —— 从 trace 起手,不要从 JVM heap dump 起手。
层级顺序
| 层 | 影响每个请求? | 何时优先调 |
|---|---|---|
| JVM heap & GC | 是 | jvm_gc_pause_seconds_max 上涨或内存使用不平稳 |
| HikariCP 连接池 | 是 | hikaricp_connections_pending > 0 或获取超时 |
| PostgreSQL 配置 + 索引 | 是 | pg_stat_statements 显示平均执行时间高 |
| Redis | 是(命中路径) | 缓存未命中率上升或淘汰计数增长 |
| 元数据缓存(Caffeine L1) | 是(读路径) | command 管线 warm SQL 数上涨 |
| 前端 bundle / code split | 仅页面加载 | First Contentful Paint 回退 |
跳过顺序是最常见的错误。"command 慢"几乎一定是 JDBC / PG / 索引问题,不是 Java 代码问题。
JVM heap 与 GC
默认假设 Spring Boot 3.5 on Java 21 + G1GC。
# 单后端节点(16 GB RAM)推荐基线
export JAVA_TOOL_OPTIONS="\
-Xms4g -Xmx4g \
-XX:MaxGCPauseMillis=200 \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:+UnlockExperimentalVMOptions \
-XX:G1ReservePercent=15 \
-XX:InitiatingHeapOccupancyPercent=35 \
-Xlog:gc*:file=/var/log/auraboot/gc.log:time,uptime,level,tags:filecount=10,filesize=50M"规则:
- 设
-Xms == -Xmx。Heap 扩缩容会暂停 JVM。最小值=最大值消除这次暂停 - Heap = RAM 的 ~25%,不是 50%+。PostgreSQL、Redis、page cache、OS 要分剩下的。16 GB 节点的合理起点是 4 GB heap
- G1 适合 AuraBoot 的负载,胜过 ZGC。绝大多数 command 不是分配密集型;G1 + 200 ms 目标比 ZGC 的亚 10 ms 目标对我们的请求 profile 更可预测
- 盯
jvm_gc_pause_seconds_max。p99 持续 > 500 ms 时,先加 heap 或排查分配压力,不要先换 collector
OOM 时 heap dump 默认开启;落到你读得到的盘上:
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/auraboot/heap-dumps/HikariCP 连接池
application.yml 默认:
spring:
datasource:
hikari:
maximum-pool-size: 30
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
idle-timeout: 600000
leak-detection-threshold: 30000容量公式(Brian Goetz 的法则在 AuraBoot 落地):
maximum-pool-size ≈ (cpu_cores × 2) + effective_spindle_count
单 8 核节点连一个 PG 实例:起点 20,只在 hikaricp_connections_pending 持续 > 0 时加到 40。超过 ~50 后 PG 自己变瓶颈——加池成员让排队更糟,不更好。
症状:
| 指标 | 含义 |
|---|---|
hikaricp_connections_pending > 0 持续 > 1s | 池耗尽;要么加 size 要么修慢查询 |
hikaricp_connections_acquire p99 > 100 ms | DB 慢或池耗尽;查 pg_stat_activity |
hikaricp_connections_max < maximum-pool-size | 池过大;缩回去把 PG slot 让出来 |
| 日志泄漏告警 | 某条路径没关连接;用 leak-detection-threshold 追 |
PostgreSQL
最重要两件事:postgresql.conf 与你建的索引。
postgresql.conf 基线(16 GB RAM 专用 PG 主机)
shared_buffers = 4GB # RAM 的 ~25%
effective_cache_size = 12GB # OS + PG cache 估算,RAM 的 ~75%
work_mem = 32MB # 每操作,乘以并行 worker
maintenance_work_mem = 512MB
max_connections = 100 # 与所有 app 节点的 HikariCP 总数匹配
wal_buffers = 64MB
checkpoint_completion_target = 0.9
random_page_cost = 1.1 # SSD
effective_io_concurrency = 200 # SSD
default_statistics_target = 100索引策略
平台索引不要删(迁移 runner 自动建):
ab_meta_*在tenant_id, code上的查找索引——支撑元数据缓存 warmupab_dyn_*表的主键 + 租户范围索引——支撑每次动态数据访问
你自己加的索引在 plugin 迁移文件里。两条规则:
pg_stat_statements里观察到的任何WHERE a = ? AND b = ?查询,加复合索引。不要指望优化器组合单列索引- 加索引前后都跑
EXPLAIN ANALYZE。一条新索引没改 plan 就是技术债
pg_stat_statements
启用:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all然后找最贵的:
SELECT mean_exec_time, calls, total_exec_time, query
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_%'
ORDER BY total_exec_time DESC
LIMIT 20;修复顺序:缺索引 → 改写 → 缓存 → 反范式。缓存原本应该建索引的东西,买来 30 天喘息,6 个月技术债账单。
Redis
AuraBoot 用 Redis 做分布式锁(BPM、自动化)、会话存储、限速桶、L2 元数据缓存。
spring:
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
timeout: 2000ms
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4盯:
| 指标 | 阈值 |
|---|---|
redis.commands.duration p99 | < 5 ms——更高是网络或 CPU 饱和 |
redis.evicted_keys 速率 | 应该 0;非 0 表示 maxmemory 太低 |
redis.connected_clients | 应该等于 pool.max-active × 节点数 + 管理连接 |
automation:trigger:* 上的锁竞争 | 自动化规则需要按租户分区 |
容量:2 GB Redis 对单租户生产够用,直到 cache + session 占用超过它。按指标规划,不要按预测规划。
元数据缓存(Caffeine L1)
平台元数据层把 command 定义、binding rule、状态图、模型定义、用户权限、命名查询、字典数据缓在 JVM 本地 Caffeine 缓存里(30 分钟 TTL,每区 10K 上限)。CacheWarmupRunner 在 ApplicationReadyEvent 时加载所有 active 租户的元数据。
所有 region 共用一个 Caffeine builder——CacheConfig 里的 cacheManager bean 统一设 maximumSize(10_000) + expireAfterWrite(30 分钟),再用 setCacheNames(...) 预建出 commandDefinitions、bindingRules、stateGraphDefinitions、modelDefinitions 等 region 名(权限类敏感 cache 走单独的 permissionCacheManager,5 分钟 TTL)。要改容量或 TTL 就改这个 bean,没有 per-region 的 YAML 旋钮:
// CacheConfig.cacheManager()
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(30))
.recordStats());症状:发布后 command 管线 SQL 数上涨。可能原因:plugin 导入或迁移把 cache 清了。修复:扩 warmup,或接受重建窗口。
Command 管线 SQL 预算
每个 HTTP 响应带 X-SQL-Count。生产预算:
| Command 形态 | Warm-cache SQL 数 |
|---|---|
| StateTransition(无 handler) | ≤ 6 |
| Action(无 handler) | ≤ 8 |
| Action + 1 handler + binding rule | ≤ 12 |
| FlowStep(BPMN 任务) | ≤ 18 |
任何 double 预算的都值得去访问日志 filter 看看——50(N+1 警告)和 100(N+1 严重)会告警。常见嫌疑:handler 懒加载列表,或 join 被换成了循环。
前端
前端性能工作主要看:
- Code splitting —— 设计器(Page / Dashboard / BPMN / Flow / Report / QueryBuilder)和重数据 plugin(xlsx、jspdf、html2canvas、recharts、reactflow)独立分块。不要急加载它们
- Debounce 输入 —— search 300 ms、filter/sort 150 ms。
useDebouncedValue和useDebouncedCallback在app/hooks/useDebouncedValue.ts - 列表页 keyset 分页 ——
pageSize=20&cursor=<last_id>让第 1000 页和第 1 页同样快;平台内部/api/dynamic/{pageKey}/list你传cursor就走 keyset - Bundle size 预算 —— vendor 块 > 500 KB gzipped 会被评审;报表设计器(~430 KB xlsx + ~390 KB jspdf)是当前上限
常见反模式
| 模式 | 为什么坑 |
|---|---|
加 HikariCP maximum-pool-size 修慢查询 | 把等待从池移到 PG;同等延迟,更差公平性 |
work_mem 调到 GB 级 | 乘以并行 worker,真实负载下 PG OOM |
| 应该建索引的地方拿 cache 顶 | 买时间欠债;先发索引 |
| "低 pause 一定好"加 ZGC | 增 CPU;不匹配 AuraBoot 的分配 profile |
生产关掉 X-SQL-Count | 在最需要 N+1 信号时把它弄丢 |
| 不测量先调优 | 几乎一定调错层 |
下一步
- 可观测性 —— 任何调优前先接好指标和 trace
- 灾备恢复 —— RTO/RPO 目标 + 演练 checklist
- 负载均衡 —— Nginx / HAProxy / Cloudflare 前置模式
- 集群模式 —— 单节点不够用时
企业版能力 —— Command 管线按阶段的分布式 trace span(Observability Pro)、租户感知的自动查询 plan 捕获、用你真实指标种子的容量规划看板,均为商业版能力。OSS 平台把上面所有指标和元数据缓存 region 都暴露出来;差的是预接线的分析面,不是数据。