性能调优

AuraBoot 的性能工作有固定顺序。先调影响每个请求的层——JVM、连接池、PostgreSQL——再调子系统专属旋钮。本页记录真正的杠杆、默认值,以及指出哪根错了的症状。它不追求穷举,它追求让你不调错层。

如果你在排查具体故障而不是建立生产基线,先读 可观测性 —— 从 trace 起手,不要从 JVM heap dump 起手。

层级顺序

影响每个请求?何时优先调
JVM heap & GCjvm_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 msDB 慢或池耗尽;查 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 上的查找索引——支撑元数据缓存 warmup
  • ab_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 上限)。CacheWarmupRunnerApplicationReadyEvent 时加载所有 active 租户的元数据。

所有 region 共用一个 Caffeine builder——CacheConfig 里的 cacheManager bean 统一设 maximumSize(10_000) + expireAfterWrite(30 分钟),再用 setCacheNames(...) 预建出 commandDefinitionsbindingRulesstateGraphDefinitionsmodelDefinitions 等 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。useDebouncedValueuseDebouncedCallbackapp/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 信号时把它弄丢
不测量先调优几乎一定调错层

下一步

企业版能力 —— Command 管线按阶段的分布式 trace span(Observability Pro)、租户感知的自动查询 plan 捕获、用你真实指标种子的容量规划看板,均为商业版能力。OSS 平台把上面所有指标和元数据缓存 region 都暴露出来;差的是预接线的分析面,不是数据。