跳到主要内容

Fulla 竞品性能基准对比设计

版本: v1.1(2026-08-15 评审修订:新增 M0 前置参数化、S5 池规模修正、GC 抖动场景钉死、Fulla 同 session 重跑、Zitadel JWT-profile 认证说明、warmup 口径对齐入仓数据) 日期: 2026-08-15 文档性质: 技术设计(Phase 0.5 落地蓝图,非代码——实施见 §七 milestone) 上游规划: [演进方案 §三 Phase 0](productization-evolution-plan:本地维护档案) P0「自托管竞品对比基准」 前置依赖: 基准设施设计(内部档案,已随 productization-evolution 目录转为本地维护) M1–M4 已交付(S1–S6 自测数据已入仓 benchmarks/results/验证对象: 调研报告 §3.1(内部档案,已转本地维护) 竞品列(Keycloak/Ory)的量级参考数字


零、TL;DR

  • 做什么:在同一台机器、同一套 wrk 阶梯、同一个 PostgreSQL 后端下压 Keycloak / Ory Hydra / Zitadel,与 Fulla 已入仓的自测数据(benchmarks/results/SUMMARY.md)产出同口径对比表。
  • 比什么:S1 discovery / S2 client_credentials / S3 introspect / S5 refresh_token / S6 userinfo 五个单步场景 + 冷启动 + 稳态 RSS + GC 抖动长跑(5 分钟 P99 时间序列——Fulla 无 GC 的核心差异化证据)。
  • 不比什么:S4 auth_code(各产品登录/consent 流程不可 wrk 统一驱动);Auth0(SaaS,无法自托管);竞品的极限调优配置(一律用官方推荐生产配置)。
  • 核心原则:公平性优先于数字好看。竞品社区会质疑,方法论必须无懈可击——同硬件、同并发阶梯、同后端、各产品官方推荐配置、脚本全部入仓可复现。
  • 验收:四家同口径对比表(QPS / P99 / 稳态 RSS / 冷启动 / GC 抖动)落盘 benchmarks/competitors/results/COMPARISON.md;第三方按 README 一键复现。

一、目标与非目标

1.1 目标

#目标衡量
G1同环境竞品对比数据:把调研报告 §3.1 的竞品列("来自各产品社区公开基准,非同环境对比")替换为同环境实测§六验收 ✅ COMPARISON.md
G2验证差异化叙事:C++ 无 GC / 低内存 / 低尾延迟的卖点是否有同环境数据支撑GC 抖动长跑 + 稳态 RSS 对比
G3可复现:第三方按 benchmarks/competitors/README.md 在同规格机器跑出误差 <15% 的数据复现门槛(沿用自测设施 AC1)
G4诚实修订:若某维度 Fulla 不领先,据实修订调研报告 §3.1/§3.2 的卖点排序报告更新

1.2 非目标

#非目标为什么 / 归属
N1Auth0 / Okta 等托管竞品SaaS 无法自托管、无法控制环境;其公开数字不可复现。仅保留在调研报告的量级参考里
N2S4 auth_code 场景对比各产品的登录页/重定向/consent 交互流完全不同,wrk 无法统一驱动(详见 §四 D4)
N3竞品极限调优一律官方推荐生产配置。极限调优对比是军备竞赛,无公信力;官方文档链接随结果附上
N4功能/协议覆盖度对比属产品能力审计(iam-architecture-audit.md),与性能无关
N5Fulla 阶梯数据重跑同 session 仍会重跑(见 §5.1 v1.1 修正:gcjitter/RSS/冷启动必采 + R7 消漂移);"复用"仅指沿用同一 runner/口径,2026-08-12 入仓数据降级为历史基线

二、背景:为什么现在做

2.1 Phase 0 已完成自测,对比是最后缺口

Phase 0(benchmark M1–M4)已于 2026-08-12 交付:S1–S6 六场景、40 个 JSON、承重验证报告(benchmarks/results/SUMMARY.md)。Fulla 自身数字已可信。

调研报告 §3.1 的竞品列(Keycloak ~10–20k QPS、Ory ~30–50k QPS)标注为"社区公开基准,非同环境对比,仅作量级参考"。没有同环境对比数据,"比 Keycloak 快 N 倍"就不能出现在任何对外物里。

2.2 Fulla 自测基线(对比基准,2026-08-12 实测)

场景稳态 QPS稳态 P99错误率
S1 discovery86,332—(低并发 <1ms)0.006%
S2 client_credentials8,9158ms0.000%
S3 introspect17,13212ms0.000%
S5 refresh_token1,98226ms0.000%
S6 userinfo16,67412ms0.000%

环境:WSL2 8 vCPU / 16GB / PG 连接池 25 / Redis 20 / wrk 4.1.0 / 阶梯 2→128。 详见 benchmarks/results/SUMMARY.md

2.3 现有可复用资产

资产复用方式
run-scenario.sh 阶梯 runner(warmup→measure→JSON)竞品场景直接传不同的 .lua;runner 加可选参数(READY_PATH/RESULTS_DIR/WRK_LIB_DIR/--reissue 钩子,见 M0.1),单一实现不复制第二份
parse-wrk.py(wrk 文本→schema v1 JSON)场景无关,仅加 --product/--product-version 透传参数(M0.2)
observe/docker-stats.sh + scrape-metrics.shdocker-stats 加 CONTAINER_GLOB 参数(M0.3)采竞品容器 RSS/CPU;scrape-metrics 仅 Fulla 可用(竞品无 /metrics 端点,观察项拆分)
measure-cold-start.sh 模式每家竞品一个等价的冷启动计时脚本
lib/gen-tokens.py 思路竞品 token 池生成(各家 introspect 的 token 须由其自身签发,见 D5)
docker-compose 底座(PG15)竞品 compose 复用同一 PG 版本与连接池配置

三、关键约束与设计决策

D1 — 公平性三同原则(同硬件 / 同工具 / 同后端)

这是整个方案的可信度基础。

维度统一值说明
硬件同一台机器(Phase 0 用的 WSL2 8 vCPU/16GB,或后续专用裸机)四家依次跑,中间 docker compose down -v 清场
OS/内核同一 WSL2 Ubuntu
压测工具wrk 4.1.0,同一份阶梯参数(2→4→8→16→32→64→128)、warmup 5s / measure 10s(与入仓自测数据口径一致;Keycloak warmup 60s,见 D2 豁免)复用 run-scenario.sh,不写第二套 runner
后端存储PostgreSQL 15(同一 image tag),连接池对齐 25竞品各自支持的连接池上限可能 <25,取 min(25, 官方上限) 并在结果中标注
网络拓扑localhost cross-container(wrk 在宿主机)与自测一致
结果格式schema v1 JSON(parse-wrk.py四家数据同构,run-comparison.sh 才能聚合
端口各家栈固定端口(fulla 5555 / Keycloak 8080 / Hydra 4444+4445 / Zitadel 8080)串行执行互不冲突;每家 setup 前置断言端口空闲

D2 — 竞品配置 = 官方推荐生产配置(不调优、不调差)

依据:极限调优对比无公信力;故意调差是学术造假。

竞品配置基线官方出处
Keycloakstart --optimized + PostgreSQL,内存按官方建议 2GB 限额Running Keycloak in a container
Ory Hydra官方 docker-compose + PostgreSQL DSNOry Hydra docs
Zitadel官方 compose(setup mode 初始化 + PostgreSQL)Set up ZITADEL with Docker Compose

每家竞品的 setup.sh 头部注释必须附官方文档链接;偏离官方默认的每一项(如连接池对齐)单独注释理由。

JVM 预热豁免:Keycloak 的 JIT/GC 需要更长预热。warmup 对 Keycloak 延长到 60s(其余三家与 Fulla 自测口径一致取 5s,测量时长统一 10s),在结果中标注——这不是偏袒,是给 JIT 编译时间,否则测的是"未编译的解释执行"。

D3 — 场景映射:功能等价,不是路径等价

各产品端点路径不同,映射到同一功能的端点(§四矩阵)。关键约束:

  • S3 introspect 的 Ory 特例:Hydra 的 introspect 在 admin 端口(4445)而非 public 端口,且生产部署中 admin 端口通常不对外。为公平,四家都测 introspect 但在 COMPARISON.md 中标注 Ory 的 admin-port 语义差异
  • 认证方式:client_credentials 用各产品的标准 client 认证(Basic 或 post,按其声明)。
  • Zitadel S2 特例(JWT profile):Zitadel 的官方 M2M 路径是 Service User + private_key_jwt(token 端点不支持 Basic 认证的 client_credentials)。wrk Lua 无法签名 JWT,故 setup 阶段用 python(pyjwt + cryptography,WSL 已具备)预签 client_assertion 池,exp 覆盖整个跑数窗口;若 Zitadel 强制 jti 单用则每档重签(等价 S5 的 --reissue 机制)。COMPARISON.md 附录注明"JWT profile 是 Zitadel 官方推荐的机器认证,功能等价"。

D4 — 排除 S4 auth_code:wrk 不可驱动

Fulla 的 S4 是 login → token 两步 form POST(headless 可驱动)。但:

  • Keycloak auth_code 需要走其登录页 HTML 表单 + JS
  • Ory Hydra 需要外部 login/consent app 配合(Hydra 本身无登录 UI)
  • Zitadel 有自己的登录会话流

统一驱动需要真实浏览器(Playwright),测的是"登录页渲染"而非"token 签发"。首期排除 S4,COMPARISON.md 注明限制;若后续需要,用"预签发 code + 单步 token 交换"近似(各产品预签发方式不同,复杂度高,Phase 0.6 再议)。

D5 — 竞品 token 池必须由其自身签发(不能 SQL 直插)

Fulla 自测的 S3/S6 用 SQL 预种 token(gen-tokens.py)。竞品不行

  • Keycloak 的 access token 是签名的 JWT,哈希/密钥格式私有
  • Hydra/Zitadel 同理

方案:每家竞品的 setup.sh 通过其自身的 token 端点批量签发 token,把活跃 token 写入 token 池文件,Lua 场景脚本复用同一套 token-pool 逻辑(线程切片,复用 s3-introspect.lua 模式)。

池规模(v1.1 修正——两类池口径不同)

  • S3/S6 池(可复用):token 只读验证不消耗,N=2000 足够;批量签发本身约 1–2 分钟(并行 xargs -P8 更快)。
  • S5 池(单发单耗):每个 refresh token 用一次即失效,池必须 ≥ 该档 QPS × 测量时长 × 1.3 余量。Fulla 自测的实测口径:20,000 池 ÷ 10s ≈ 1,982 QPS(池刚好覆盖测量窗口)。竞品每档前须重发池run-scenario.sh 新增 --reissue "<cmd>" 钩子,等价自测的 SQL --reseed,但走各家 API)。
  • RT 不能来自 client_credentials:RFC 6749 §4.4.3 规定该 grant 不得签发 refresh token。竞品 RT 池须经用户上下文流程获取——Keycloak 用 ROPC(direct access grants);Hydra 用 accept 流(见 M2);Zitadel 用 Session API/auth_code(见 M2)。

D6 — GC 抖动 = 长跑 P99 时间序列(Fulla 核心差异化证据)

单次 30s 跑看不出 GC 周期。设计专门的长跑测试

每家:c=32 固定,持续 5 分钟,场景钉 S6 userinfo
(v1.1 修正:S5 不可用——池会耗尽;S2 有写放大——Fulla 每请求落库一条 token,
5 分钟 ~8k QPS ≈ 240 万行,对四家工作负载构成不对称;S6 是读路径、token 池可复用、
四家功能等价,是最公平的载波。Hydra 若 S6 标 N/A 则降级 S2 并在结果中标注。)
采集:每 10s 窗口记录一次该窗口的 P99(wrk 不支持原生分段 → 30 个串行 10s 段近似)
输出:P99 随时间的曲线(JSON 数组)
预期:Keycloak 出现周期性 P99 尖峰(GC STW);Ory 出现 Go GC 小尖峰;Fulla 平线

实现:benchmarks/competitors/run-gc-jitter.sh——循环调用 wrk -d10s 30 次串联,每段 parse 出 P99 存数组。这是对外叙事最有力的一张图("零 GC 抖动"的可视化证据)。

D7 — 内存口径统一:容器全栈 RSS + 标注逻辑层

Phase 0 承重验证发现 Fulla 容器 RSS ~2.4GB(含 Drogon 连接池/共享库 COW)与"50–120MB"声称口径不匹配。对比方案统一口径:

口径采集用途
容器全栈 RSSdocker stats 稳态采样(复用 observe 脚本)对比表主口径——四家同口径可比
进程 PSS(可选)smem / /proc/*/smaps_rollup消除 COW 共享页重复计数的补充口径

在 COMPARISON.md 明确写"全栈容器 RSS,含各自运行时+连接池",避免口径争议。


四、对比场景矩阵

4.1 场景 × 竞品端点映射(已按官方文档核实)

场景功能FullaKeycloakOry HydraZitadel
S1 discoveryOIDC 发现文档GET /.well-known/openid-configurationGET /realms/{r}/.well-known/openid-configurationGET /.well-known/openid-configuration (public :4444)GET /.well-known/openid-configuration
S2 client_credentials机器间 tokenPOST /oauth2/tokenPOST /realms/{r}/protocol/openid-connect/tokenPOST /oauth2/token (public :4444)POST /oauth/v2/token
S3 introspecttoken 内省POST /oauth2/introspectPOST /realms/{r}/protocol/openid-connect/token/introspectPOST /admin/oauth2/introspect (admin :4445) ⚠POST /oauth/v2/introspect
S5 refresh_tokentoken 刷新POST /oauth2/token (refresh)POST /realms/{r}/protocol/openid-connect/token (refresh)POST /oauth2/token (refresh)POST /oauth/v2/token (refresh)
S6 userinfo用户信息GET /oauth2/userinfoGET /realms/{r}/protocol/openid-connect/userinfoGET /userinfo (public :4444)GET /oidc/v1/userinfo
S4 auth_code用户登录login→token登录页不可 headless 驱动需外部 consent app登录会话流

⚠ Ory introspect 的 admin-port 语义差异在结果中标注。Keycloak realm 名、Hydra public/admin 双端口、Zitadel 的 instance domain 都在各自 setup 脚本中固定为常量。

端点出处:

4.2 指标矩阵

指标采集方式对比表列
稳态 QPS(err<0.01% 最高档)wrk 阶梯 + parse-wrk.py
P50 / P95 / P99(稳态档)wrk --latency
错误率wrk non-2xx✅(门槛列)
冷启动(→health 200)各家等价计时脚本
稳态容器 RSSdocker stats 采样
GC 抖动(5min P99 曲线)run-gc-jitter.sh✅(专项小节)
driver CPUrun-scenario.sh 现有采样✅(可信度标注)

五、测试策略

5.1 执行顺序(单机串行,防互相干扰)

1. Fulla — 同 session 重跑(v1.1 修正:gcjitter/RSS/冷启动是 AC1/AC2 的必采项,
Phase 0 未采过 gcjitter;且 R7 要求四家同一 session 连续跑以消除跨日
环境漂移。2026-08-12 入仓数据保留为历史基线,同 session 新数据用于对比)
2. Keycloak — setup → 阶梯 → 长跑 → 冷启动 → teardown
3. Ory Hydra — 同上
4. Zitadel — 同上
每家之间 docker compose down -v + 确认无残留容器/卷/网络

run-comparison.sh --fresh 全串行执行;--only keycloak 支持单家补跑。

5.2 竞品 setup 约定(每家一个 setup.sh)

每个 setup.sh 必须:

  1. 启动该竞品 + PostgreSQL(对齐连接池,见 D1)
  2. 等待健康(各家的 ready 探针:Keycloak /realms/master、Hydra /health/ready、Zitadel /debug/healthz
  3. 初始化配置(realm/client/用户——用各家 CLI 或 admin API)
  4. 批量签发 token 池(D5:N 次 client_credentials 请求 → access_tokens.txt
  5. warmup 校验(一个 token 请求确认管线通)

5.3 长跑 GC 抖动(D6)

参数:c = 各家稳态拐点档,30 × 10s 段(共 5 分钟),段间无间隔。 输出:results/<date>-<sha>-<product>-gcjitter.json(P99 数组 + 段时间戳)。

5.4 冷启动

各家独立计时:docker compose up -d <idp> → 轮询 ready 探针 200。 记录两模式(含/不含 DB 预热)与自测 measure-cold-start.sh 对齐。

5.5 环境元数据

结果 JSON 的 env 块沿用 schema v1,新增 product / product_version 字段(parse-wrk.py 加一个透传参数),COMPARISON.md 表头列出版本。


六、验收标准(可勾选)

✅ 2026-08-17 全部通过(M0–M3 交付,四产品同 session 实测 2026-08-17,COMPARISON.md 由 gen-comparison.py 生成)。 🔄 2026-08-21 全量重跑刷新(同一设施,Fulla 换性能优化后基准档——wave-1/2 + LTO,见 G1 交付注):五场景全部领先(此前 S5/S6 落后——S5 系测量预算伪影已修口径、S6 经 wave-2 用户/角色缓存反超);GC 节新增跨产品环境噪声互证(四家同款尖峰)。调研报告 §3.1 已同步引用新表。

#验收项衡量状态
AC1四家同口径对比表:S1/S2/S3/S5/S6 × {QPS, P99, RSS, 冷启动} 入仓 benchmarks/competitors/results/COMPARISON.md,每行带产品版本号COMPARISON.md
AC2GC 抖动曲线:四家 5 分钟 P99 时间序列 JSON + 对比小节(Fulla 是否平线、Keycloak 是否有周期尖峰)gcjitter JSON + 小节✅(结论与预期相反,见 G4 注记:GC 语言全平线、Fulla 有环境层秒级尖峰——已在报告与研究报告中诚实修订)
AC3可复现run-comparison.sh 一键串行跑四家;README 含环境要求与复现步骤复现指引✅(benchmarks/competitors/README.md
AC4公平性声明:每家竞品的配置来源(官方文档链接)、偏离默认的每一项、warmup 差异(Keycloak 60s)均显式标注COMPARISON.md 附录 + setup.sh 注释
AC5诚实修订:调研报告 §3.1 竞品列更新为"同环境实测",§3.2 卖点若被证伪则收敛research.md 更新✅(S5/S6 与 GC 抖动主张按实测收敛,见 §3.1/3.2 修订)
AC6S4 排除声明:COMPARISON.md 注明 auth_code 场景的方法限制限制小节✅(附录 B.1)

实施勘误记录(v1.2,2026-08-17 落地时修订;v1.3 增补 2026-08-18 两项)

实施过程中偏离本设计 v1.1 的决策,全部为公平性/可行性修正:

  1. Zitadel 版本 v2.71.19 → v4.17.1:v2.71 已落后两个大版本(当前稳定线 v4,与 Keycloak 26 / Hydra 26 同代),且 v4 为 eventstore/投影性能重写。测旧版会失真贬低 Zitadel,不可辩护。首次尝试的 v1.80.0-v2.9-amd64 tag 实为 v1 时代 CockroachDB-only 镜像,废弃。
  2. Zitadel S2 认证路径修正:设计 D3 原表述"client_assertion(JWT profile)"在 v2.71/v4 实测均不可用——Zitadel token 端点对机器用户的 client_credentials 只走 Basic-secret(每请求哈希),官方 M2M 路径是 RFC 7523 jwt-bearer 授权grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=…)。S2/S3 的等价性标注已按此更新。
  3. Zitadel S3 认证换私钥:#6220 官方建议——secret 认证每请求做密码哈希(CPU 瓶颈);改用 OIDC app + private_key_jwt。实测对比:Basic 路径 S3 无法过错误门,私钥路径 2.9k QPS 零错误。
  4. 投影平复门:mint ~2000 token 后立即开压,Zitadel CQRS 投影追赶会使 S1 出现 500 风暴(c=16 达 99.99% 错误)。run-all 前置 discovery 冒烟门(clean 才开跑),消除该伪影。
  5. S5 Zitadel = N/A(DG-2 提前裁决):机器用户无 refresh token(RFC 6749 §4.4.3)、password grant 已移除、Session API→auth_code 属用户交互流(D4 同类排除理由)。限制小节注明。
  6. docker-stats.sh 竖线 glob 回归修复:bash 5.2 下 case $x in $GLOB)(GLOB 含 |)不再展开为多分支——Fulla 侧 RSS 采样自 M0 参数化起一直空采;改为拆分逐个匹配。该回归同时解释了 v1.1 后 Fulla RSS 数据缺失。
  7. Fulla S2 scope 跟随 #43:seed 摒弃 legacy read/write,bench 校验与 s2 lua 改用 tokens:read(同码同测,非口径变化)。
  8. (v1.3)四产品 PG 15 → 17 同步升级(2026-08-18,f789bda):设计基线为"与自测相同 tag 的 postgres:15-alpine"。偏离动机:D1 同环境公平——维持四产品同 PG 大版本;Fulla 服务端 libpq 已是 17.x,client 17/server 15 的错位消除。Fulla 自身 15 vs 17 A/B 在噪声带内(无自身吞吐诉求)。deploy/ 的 compose 与 Helm 同步到 17(存量卷升级 runbook 见 docs/operate/postgresql-major-upgrade.md,CHANGELOG 已标 BREAKING)。
  9. (v1.3)Fulla Redis 池 25 → 64(2026-08-18,快赢 8838ac6):设计 §5.2 连接池口径按 pool=25 对齐;实施时 cache-on 要求池 ≥ 预期并发(pool 20 时 S6 全连接超时 -18%),bench overlay 调至 64。竞品各按自家官方默认池口径运行(在 COMPARISON.md 公平性附录注明)。

七、实施计划(4 个 milestone,每步带验收标准)

v1.1:M0 为评审新增前置——共享设施的参数化改造(向后兼容,不带新 env 时行为不变)。

M0 — 共享设施参数化(前置,~0.5 天)

#步骤内容验收标准
M0.1run-scenario.sh 参数化(a) READY_PATH env(默认 /health/ready)——健康门探针可换竞品探针;(b) RESULTS_DIR env(默认 benchmarks/results);(c) WRK_LIB_DIR 导出改为 ${WRK_LIB_DIR:-$BENCH_DIR/lib}(竞品 lua 指向自家 lib);(d) BENCH_PRODUCT/BENCH_PRODUCT_VERSION env 透传给 parse-wrk.py;(e) 通用 --reissue "<cmd>" 钩子:每档 warmup 前与 measured 前各执行一次(竞品 S5 用 API 重发 token 池,替代 Fulla 的 SQL --reseed);(f) --observe 拆为 --observe-stats(仅 docker-stats)与 --observe-metrics(仅 scrape-metrics;竞品无 /metrics 不用)AC-M0.1a 不带任何新 env 跑 Fulla S1 单档(c=2, -d10s):行为与改造前一致(JSON schema、默认值、输出路径全不变);AC-M0.1b READY_PATH=<任意 200 路径> RESULTS_DIR=<tmp> BENCH_PRODUCT=x 时健康门走新路径、JSON 落新目录且 env.product=x;AC-M0.1c --reissue "touch $TMP/marker" 冒烟:每档产生两个 marker(warmup 前+measured 前)
M0.2parse-wrk.py 透传--product/--product-version CLI 参数 → env 块新增 product/product_version 字段(缺省 fulla/空)AC-M0.2 wrk 样例文本经管道解析,JSON 含两字段且值正确;不传参时缺省值不破坏现有聚合
M0.3docker-stats.sh 容器过滤参数化CONTAINER_GLOB env(默认保持 `fulla-backendfulla-postgres
M0.4schema 文档同步result-schema.md env 块补 product/product_version 字段说明文档字段表与实现一致(对照检查)

M1 — Keycloak 对比(最重,先啃硬骨头,~2 天)

配置基线(D2):quay.io/keycloak/keycloak:<pin 版本> + start --optimized + --memory 2G(官方容器建议),PostgreSQL 用与自测相同 image tagpostgres:15-alpine,端口 8080。

#步骤内容验收标准
M1.1docker-compose.ymlkeycloak + postgres;healthcheck 打 /realms/master;卷/网络显式前缀 kc-bench-AC-M1.1a up -d 后探针 200;AC-M1.1b down -vdocker ps -a/docker volume ls/docker network ls 无 kc-bench 残留
M1.2setup.sh等健康 → kcadm.sh 建 realm bench、client bench-svc(confidential + service account + introspection 权限)、user bench-user(direct grant)→ 校准跑(c=8 单档 S2/S5/S6 估 QPS)→ 按 池 = QPS×10s×1.3 生成 RT 池(ROPC 批量签发,xargs -P8 并行)+ AT 池 ≥2000(S3/S6 复用)→ warmup 验证一发 token 请求AC-M1.2a set -euo pipefail,任何 kcadm/curl 失败即非零退出;AC-M1.2b 结尾自检:两个池文件行数 ≥ 期望、单发 client_credentials 得 200;AC-M1.2c 幂等:down -v 后重跑 setup 成功
M1.3scenarios/(5 个 lua)s1/s2/s3/s5/s6 按 §4.1 端点改写;S2 用 Basic(bench-svc);S3 Basic + AT 池(线程切片,复用 s3 模式);S5 RT one-shot 池;S6 Bearer AT 池AC-M1.3 每个场景 wrk c=2 -d5s 冒烟:非 2xx=0、socket 错误=0(S5 允许池尾 nil 关连接,但不得有 invalid_grant)
M1.4阶梯数据WARMUP_S=60 DURATION_S=10 阶梯 2→128 × 5 场景 → 35 个 JSON;S2 档挂 --observe-stats 采 RSSAC-M1.4a 35 JSON 全带 product=keycloak + 版本;AC-M1.4b 每档 driver CPU <80% 或 JSON 标 limited=trueAC-M1.4c RSS tsv 落盘且含 keycloak+postgres 行
M1.5GC 抖动run-gc-jitter.sh:S6 c=32,30×10s 段(D6)AC-M1.5 JSON 含 30 个 P99 数据点 + 段起始时间戳;无段失败
M1.6冷启动两模式:A=全新卷完整初始化(含 realm/client 建立);B=预初始化卷仅重启 keycloak 容器AC-M1.6 2 个 JSON(mode A/B),含秒数与 RSS 峰值
M1.7teardown.shdown -v + 残留断言同 AC-M1.1b
M1.8首版两方对比Fulla 同 session 重跑(§5.1)+ Keycloak 数据 → 草稿对比表AC-M1.8 5 场景 × QPS / P50/P95/P99 / RSS / 冷启动 行齐、版本列齐

M2 — Ory Hydra + Zitadel(~3 天)

Hydra:无内建用户体系。S5/S6 的用户 token 用官方 mock 模式 headless 驱动:GET /oauth2/auth(login 跳转)→ admin API POST /admin/oauth2/auth/requests/login/accept + consent/accept → code → token,纯 curl 可驱动(无需浏览器)。 Zitadel:S2 走 JWT profile 预签 assertion 池(D3 特例);S3 用 API client + Basic;S5/S6 优先 v2 Session API 建 password 会话 → auth_code 换用户 token。

#步骤验收标准
M2.1Hydra compose(hydra v2 + PG,public 4444 / admin 4445)+ setup(client create + accept 流驱动签池)同 M1.1/M1.2 模式:探针 /health/ready 200;池文件行数自检;任何 curl/jq 失败非零退出
M2.2Hydra scenarios + 阶梯 + gcjitter + 冷启动同 M1.3–M1.6 验收;S3 JSON/结果带 admin-port 标注
M2.3Zitadel compose(setup mode 初始化 + start mode 运行)+ setup(机器用户 JSON key、API client、人类用户)同 M1.1/M1.2;setup 两阶段(init/start)可分别重入
M2.4Zitadel scenarios + 阶梯 + gcjitter + 冷启动同 M1.3–M1.6;S2 结果注明 JWT-profile 认证等价性

决策门(M2 内)

  • DG-1:Hydra accept 流若 curl 驱动不成(如强制 JS),S5/S6 标 N/A。判据:setup 能稳定取得带 openid scope 的用户 token。
  • DG-2:Zitadel Session API→auth_code 若 2 个工作日内驱动不成,S5/S6 标 N/A(S1/S2/S3 保底),限制小节写明。

M3 — 汇总 + 诚实修订(~1 天)

#步骤验收标准
M3.1gen-comparison.py 聚合器读四家 JSON 全自动生成 COMPARISON.md:主表(5 场景 × QPS/P50/P95/P99/RSS/冷启动 × 版本列)、GC 抖动小节、公平性附录(配置出处 + 偏离项 + warmup 差异)、限制小节(S4 排除、Ory admin-port、Zitadel JWT-profile、WSL2 声明)。AC-M3.1:无手填数字;缺某家某场景时显式 N/A 不缺行
M3.2run-comparison.sh 编排器--fresh 全串行(每家之间清场+残留断言)+ --only <product> 补跑 + 末尾自动调聚合器。AC-M3.2:一条命令从空环境到 COMPARISON.md
M3.3research.md §3.1/§3.2 修订竞品列改"同环境实测";卖点按实测收敛。AC-M3.3:每个数字可溯源到入仓 JSON
M3.4文档收尾benchmarks/README.md 增 competitors 指引;本文档 §六验收勾选

八、目录结构设计(仅设计)

benchmarks/competitors/
├── README.md # 复现指引(环境/顺序/限制)
├── run-comparison.sh # 串行跑全部(或 --only <product>)
├── run-gc-jitter.sh # 5 分钟 P99 时间序列(D6)
├── keycloak/
│ ├── docker-compose.yml # Keycloak + PG(对齐 D1)
│ ├── setup.sh # start --optimized + kcadm 初始化 + token 池
│ ├── teardown.sh
│ ├── lib/generated/ # setup 产物:token 池文件(WRK_LIB_DIR 指向此处)
│ └── scenarios/ # s1/s2/s3/s5/s6.lua(端点按 §4.1)
├── ory/
│ ├── docker-compose.yml # Hydra(+Kratos 如需) + PG
│ ├── setup.sh # hydra client create + token 池
│ ├── teardown.sh
│ └── scenarios/
├── zitadel/
│ ├── docker-compose.yml
│ ├── setup.sh # setup mode 初始化 + token 池
│ ├── teardown.sh
│ └── scenarios/
└── results/
├── <date>-<sha>-<product>-<scenario>-c<conn>.json # 同 schema v1
├── <date>-<sha>-<product>-gcjitter.json
└── COMPARISON.md # 汇总对比表(gen-comparison.py 生成,M3)

# 聚合器放共享 reporting/(与 parse-wrk.py 同层):
benchmarks/reporting/gen-comparison.py

复用不复制run-scenario.sh / parse-wrk.py / observe/ 直接引用 benchmarks/fulla/benchmarks/reporting/ 的现有实现(通过路径参数或环境变量),不为竞品复制第二份 runner。


九、风险与缓解

风险等级影响缓解
竞品社区质疑配置不公平对外数据被推翻,信誉受损D2 官方推荐配置 + AC4 全量标注偏离项;发布前可请竞品社区 review setup 脚本
Keycloak JIT/GC 预热不足,数字偏低被指不公平Keycloak 数字虚低warmup 延长 60s + 长跑前置 1 分钟丢弃段
Hydra 无内建用户,S6 不可测场景覆盖缺口Hydra 配最小 login/consent mock app(官方 brownfield 模式);若复杂度超预期,S6 对 Hydra 标 N/A
S5 竞品池规模不足/重发太慢S5 数据失真或 setup 超时校准跑定池规模(QPS×10s×1.3);xargs -P8 并行签发;--reissue 每档重发(D5 v1.1)
Zitadel token 端点不支持 Basic client_credentialsS2 无法按统一 Basic 口径测JWT profile 预签 assertion 池(官方推荐路径,pyjwt 签发);COMPARISON 注明认证等价性(D3 v1.1)
Zitadel Session API→auth_code 驱动失败S5/S6 缺口决策门 DG-2:2 个工作日不成标 N/A,S1/S2/S3 保底
竞品 token 池签发慢(2000 次 API 调用)setup 时间长并行签发(xargs -P8);或降池到 500(S3/S6 池可复用,量够)
Fulla 某维度不领先卖点叙事受损这正是设施价值——诚实收敛到领先维度(演进方案 §二原则 1 的既定预案)
单机串行跑,环境漂移(系统更新/温度)四家数据不同批不可比同一 session 内连续跑完;每家结果带时间戳;复跑取中位数
wrk 打不满 Go/Java 服务(driver 受限)数字是下限沿用 AC4 driver CPU 门;超 80% 标注

附录 A:与上游文档的关系

上游关系
基准设施设计(内部档案,已随 productization-evolution 目录转为本地维护)本文档是其 N1(Phase 0.5 竞品对比)的展开;复用其 runner/parser/schema/observe
[演进方案](productization-evolution-plan:本地维护档案) §三 Phase 0 P0 第 2 项本文档是该工作项的落地设计
调研报告(内部档案) §3.1竞品列的替换数据源;M3 据实修订
benchmarks/results/SUMMARY.mdFulla 侧基线(同环境自测,2026-08-12)

附录 B:决策记录

决策选择备选与否决理由
对比对象Keycloak / Ory / ZitadelAuth0(SaaS 不可自托管)否决
场景范围S1/S2/S3/S5/S6S4(登录流不可 headless 驱动)排除,详见 D4
配置基线官方推荐生产配置极限调优(军备竞赛无公信力)与默认 dev 配置(不公平)均否决
token 池各家 API 自签发SQL 直插(签名格式私有)否决,详见 D5
内存口径容器全栈 RSS(主)+ PSS(补充)单一口径必有一方吃亏,双口径并列标注
warmup/measure 口径(v1.1)5s/10s,Keycloak warmup 60s与入仓自测数据一致;原文"其余四家 10s"与实际数据(5s/10s)不符,已修正
GC 抖动载波场景(v1.1)S6 userinfo,c=32 固定S5(池耗尽)与 S2(Fulla 每请求写库、负载构成不对称)否决,详见 D6
Fulla 数据口径(v1.1)同 session 重跑"复用旧数据"与 AC2(gcjitter 必采)+ R7(同 session 消漂移)矛盾,已修正
S5 竞品池(v1.1)每档 API 重发(--reissue),池=QPS×10s×1.3固定 2000 池(单发单耗会耗尽)否决;RT 不取自 client_credentials(RFC 6749 §4.4.3 禁止)