ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Permify 负载压测实战指南:基于 k6 与 GKE/Postgres 的 1000 VU / 10000 RPS 基准测试方案

Permify 负载压测实战指南:基于 k6 与 GKE/Postgres 的 1000 VU / 10000 RPS 基准测试方案 Permify 负载压测实战指南基于 k6 与 GKE/Postgres 的 1000 VU / 10000 RPS 基准测试方案【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 项目地址: https://gitcode.com/GitHub_Trending/pe/permify导读本文完整呈现 Permify 官方负载压测方案文档见 docs/performance-test/README.md包含一套覆盖用户关注、内容可见性、互动父子层级等真实场景的测试 Schema、基于 Grafana k6 的批量数据种子脚本与峰值压测脚本以及实测的 74614 次权限检查请求的完整指标结果。读完本文你将能够复刻这套先灌数据、再打压测的完整流程并理解 Permify 在check请求路径上的并发模型、限流与缓存机制对压测结果的影响。1. 测试目标与测试环境这份文档的定位是性能基准测试的参考指南目标是验证 Permify 在高并发授权请求下的吞吐与延迟表现。官方压测的规模设定为1000 VU并发虚拟用户与 10000 RPS每秒请求数压测请求类型为权限检查check。测试环境由以下组件构成组件配置Permify 版本1.6.9集群Google Kubernetes EngineGKEgeneral-purpose 机型数据库Postgres 15部署于 Google Cloud SQLHelm 配置docs/performance-test/values.yaml其中values.yaml保存了压测环境使用的默认配置Helm values、资源设置等也是理解压测调优的关键文件。值得注意的是该文件中的镜像 tag 为v1.3.0而 README 声明的压测版本为 1.6.9实际使用时请以你部署的镜像 tag 为准。1.1 压测环境关键配置解读values.yamlvalues.yaml 中与压测表现直接相关的配置点如下副本与弹性autoscaling.minReplicas: 5、maxReplicas: 15允许在 10000 RPS 峰值下自动扩容资源配额resources.limits.cpu: 300m / memory: 600Mi请求配额150m / 300Mi说明压测目标是让小规格 Pod 在水平扩展下承担高负载服务端口HTTP 网关端口3476gRPC 端口3478与serve命令默认端口一致见 pkg/cmd/serve.go限流server.rate_limit: 10000即服务端每秒最多处理 10000 个请求——这与压测目标 10000 RPS 峰值严格对齐限流阈值刚好覆盖全量负载缓存schema 缓存number_of_counters: 1000 / max_cost: 24MiBpermission 缓存number_of_counters: 10000 / max_cost: 256MiB其中 permission 缓存专门服务于check引擎对压测延迟影响最大并发控制permission.concurrency_limit: 1000限制授权检查引擎的并发执行数数据库Postgres 引擎读写分离 URImax_connections: 1、min_connections: 1连接池auto_migrate: truegarbage_collection开启间隔 7200s、窗口 7200s、超时 5mmax_data_per_write: 10000认证authn.method: preshared预共享密钥对应压测脚本中携带的Authorization: Bearer your-token分布式distributed.enabled: true端口5053即压测在分布式多节点一致性哈希负载均衡模式下进行。2. 压测使用的 Permify Schema压测前需要先写入一套 Schema。官方压测 Schema 定义了三种实体及其权限关系entity user { relation self user relation follower user relation blocked user attribute is_public boolean permission view self or (is_public or follower) not blocked } entity content { relation owner user attribute is_public boolean permission view owner.self or (is_public or owner.follower) not owner.blocked } entity interaction { relation creator user relation parent content permission view creator.self or (creator.view and parent.view) }这套 Schema 的压测价值在于覆盖了 Permify 授权模型中的几类核心计算模式关系直接判断user.view self命中单条关系元组ABAC 属性参与is_public or followerboolean 属性与关系元组混合运算排除语义EXCLUSION... not blocked对应check引擎的 exclusion 组合器跨实体关系引用owner.self、owner.follower触发对user实体定义的递归解析递归权限组合interaction.view creator.self or (creator.view and parent.view)包含 UNION 与 INTERSECTION 组合器的嵌套求值。对应到源码实现internal/engines/check.go会针对 UNION / INTERSECTION / EXCLUSION 三类组合器并行执行子检查函数并通过concurrencyLimit限制并发度见 internal/engines/check.go。因此这套 Schema 能真实测出check引擎在组合逻辑下的并发调度与递归解析能力。3. 数据种子脚本Write Script压测之前需要先为 Schema 灌入数据。官方通过一个一次性的 k6 脚本完成数据种子Seed工作单次执行写入100000 个 user、100000 个 content、100000 个 interaction对应的关系元组与属性。3.1 完整种子脚本import http from k6/http; import { fail } from k6; export const options { vus: 1, iterations: 1 }; const TENANT_ID tenant-id; const url your-api-endpoint; const TOTAL_IDS 100000; const BATCH_SIZE 1000; function writeInBatches(items, key) { for (let start 0; start items.length; start BATCH_SIZE) { const chunk items.slice(start, start BATCH_SIZE); const batchNumber start / BATCH_SIZE 1; const res http.post( ${url}/v1/tenants/${TENANT_ID}/data/write, JSON.stringify({ metadata: { schema_version: schema-version }, [key]: chunk, }), { headers: { Content-Type: application/json }, } ); if (res.status 200 || res.status 300) { console.error(\nPOST /data/write ${key} batch ${batchNumber} FAILED status${res.status}\nbody\n${res.body}\n); fail(POST /data/write ${key} batch ${batchNumber} non-2xx); } } } export default function () { const tuples []; const attributes []; for (let i 0; i TOTAL_IDS; i) { const id String(i); const nextId String((i 1) % TOTAL_IDS); const blockedId String((i 2) % TOTAL_IDS); const isPublic i % 4 0; tuples.push( { entity: { type: user, id }, relation: self, subject: { type: user, id, relation: }, }, { entity: { type: user, id }, relation: follower, subject: { type: user, id: nextId, relation: }, }, { entity: { type: user, id }, relation: blocked, subject: { type: user, id: blockedId, relation: }, }, { entity: { type: content, id }, relation: owner, subject: { type: user, id, relation: }, }, { entity: { type: interaction, id }, relation: creator, subject: { type: user, id, relation: }, }, { entity: { type: interaction, id }, relation: parent, subject: { type: content, id, relation: }, } ); attributes.push( { entity: { type: user, id }, attribute: is_public, value: { boolean: isPublic }, }, { entity: { type: content, id }, attribute: is_public, value: { boolean: isPublic }, } ); } writeInBatches(tuples, tuples); writeInBatches(attributes, attributes); console.log(Seeded ${TOTAL_IDS} users, ${TOTAL_IDS} contents, and ${TOTAL_IDS} interactions); }3.2 种子脚本要点解读批量写入BATCH_SIZE 1000每批 1000 条元组或属性通过一次POST /v1/tenants/{tenant_id}/data/write提交共 600 批元组 200 批属性若某批返回非 2xx 状态码则调用fail立即终止保证种子数据完整数据构造规律nextId (i 1) % TOTAL_IDS使每个用户恰好关注下一位用户形成环形关注链blockedId (i 2) % TOTAL_IDS使每个用户恰好拉黑下下位用户isPublic i % 4 0让 25% 的 user 与 content 是公开的三元组覆盖每个 id 循环生成 6 条元组user.self / user.follower / user.blocked / content.owner / interaction.creator / interaction.parent与 2 条属性user.is_public / content.is_public完整覆盖 Schema 中全部关系与属性。3.3 底层处理链路源码佐证种子脚本打到的data/write接口在服务端由DataServer.Write处理见 internal/servers/data_server.go其处理流程与压测数据质量直接相关Schema 版本解析若请求未携带metadata.schema_version服务端会读取当前租户的 head versionHeadVersion去重对元组与属性分别以实体关系/属性为 key 建立 map 去重重复写入会被跳过逐条校验每条元组通过validation.ValidateTuple、每条属性通过validation.ValidateAttribute校验——校验依赖ReadEntityDefinition读取对应 schema 版本下的实体定义所以必须确保写入的schema_version与已部署 Schema 匹配否则批量写入会失败落库返回快照校验通过后由dataWriter.Write写入数据库并返回SnapToken。压测环境中max_data_per_write: 10000见 values.yaml限制了单次写入的数据量种子脚本按 1000 一批远低于该上限。REST 路由路径/v1/tenants/{tenant_id}/data/write由 gRPC-Gateway 生成定义见 pkg/pb/base/v1/service.pb.gw.go。4. k6 压测脚本Test Script数据种子完成后使用下面的 k6 脚本对check接口进行负载测试。脚本以ramping-arrival-rate执行器从 10 RPS 逐步爬升至 10000 RPS 峰值。4.1 完整压测脚本import http from k6/http; import {check, sleep} from k6; export let options { scenarios: { contacts_load: { executor: ramping-arrival-rate, startRate: 10, // starting rate of new iterations per timeUnit timeUnit: 1s, // new iterations per second preAllocatedVUs: 50, // minimum number of VUs before the test starts maxVUs: 100, // maximum number of VUs during the test stages: [ {target: 100, duration: 10s}, // Warm-up phase {target: 1000, duration: 30s}, // Warm-up phase {target: 10000, duration: 1m }, // Ramp up to full load ] } } }; function getRandomId() { return Math.floor(Math.random() * 100000).toString(); } let reuseIdProbability 0.1; let currentEntityId getRandomId(); let currentSubjectId getRandomId(); export default function () { let entityId, subjectId; const entityType Math.random() 0.5 ? content : interaction; // Decide whether to reuse the current ID for the entity if (Math.random() reuseIdProbability) { entityId currentEntityId; // Reuse the existing entity ID } else { entityId getRandomId(); // Generate a new entity ID currentEntityId entityId; // Update current entity ID } // Decide whether to reuse the current ID for the subject if (Math.random() reuseIdProbability) { subjectId currentSubjectId; // Reuse the existing subject ID } else { subjectId getRandomId(); // Generate a new subject ID currentSubjectId subjectId; // Update current subject ID } const url your-api-endpoint; const payload JSON.stringify({ metadata: { snap_token: , schema_version: schema-version, depth: 20 }, entity: { type: entityType, id: entityId, }, permission: view, subject: { type: user, id: subjectId }, page_size: 20 }); const params { headers: { Content-Type: application/json, Authorization: Bearer your-token }, timeout: 360000 }; let response http.post(url, payload, params); //console.log(response); check(response, { is status 200: (r) r.status 200 }); sleep(1); }4.2 脚本设计意图解读场景配置ramping-arrival-rate配置项值含义startRate10初始每秒新增迭代数timeUnit1s速率单位preAllocatedVUs50测试开始前预分配的 VU 数maxVUs100测试期间最大 VU 数stages[0]10s 爬升至 100 RPS预热第一阶段stages[1]30s 爬升至 1000 RPS预热第二阶段stages[2]1m 爬升至 10000 RPS攀升至全量负载这种两段预热 一段满载的设计是为了让 Permify 的缓存与连接池先充分预热再评估满负载下的真实表现。请求数据设计实体类型分流Math.random() 0.5决定检查content还是interaction的view权限——interaction的view会递归触发creator.view and parent.view从而混合简单与复杂两类检查路径ID 复用策略以 10% 概率reuseIdProbability 0.1复用上一轮的实体/主体 ID。这是一种典型的局部性压测手法少量 ID 被高频命中从而利用 Permify 的 permission 缓存模拟真实业务中的热点数据depth: 20限制授权检查的递归深度。测试代码如 internal/engines/cache/check_test.go与压测均使用 20说明这是校验递归终止的常用取值page_size: 20分页大小参数用于检查响应中的分页语义snap_token: 空快照令牌表示使用最新数据快照执行检查Authorization: Bearer your-token与 values.yaml 中authn.method: preshared对应压测环境开启了鉴权需提供有效的预共享密钥。4.3 服务端限流对压测的影响源码佐证values.yaml 中server.rate_limit: 10000直接作用于服务端限流中间件。其实现基于令牌桶算法见 internal/middleware/limiter.gofunc NewRateLimiter(reqPerSec int64) *RateLimiter { fillInterval : time.Second / time.Duration(reqPerSec) bucket : ratelimit.NewBucket(fillInterval, reqPerSec) ... } func (l *RateLimiter) Limit(_ context.Context) error { tokenRes : l.bucket.TakeAvailable(1) if tokenRes 0 { return fmt.Errorf(reached Rate-Limiting %d, l.bucket.Available()) } return nil }令牌桶容量与速率均为reqPerSec这里即 10000。压测峰值恰好等于限流阈值因此脚本需要精确控制到达速率一旦瞬时超发就会被 429 式限流错误打断。该限流值可通过permify serve --server-rate-limit参数或PERMIFY_RATE_LIMIT环境变量调整见 pkg/cmd/flags/serve.go。此外check引擎的permission.concurrency_limit: 1000对应 internal/engines/check.go 中的concurrencyLimit决定了 UNION / INTERSECTION / EXCLUSION 子检查的并行调度上限是影响高并发下check延迟的另一关键旋钮。5. 运行步骤5.1 启动 Permify本地运行 Permify默认 HTTP 端口为http://localhost:3476与 pkg/cmd/serve.go 的默认值一致在压测前确认已按 docs/performance-test/values.yaml或等价的运行参数配置好数据库、限流、缓存、鉴权与分布式相关选项将k6test.js中的your-api-endpoint替换为实际的 API 地址本地为http://localhost:3476集群部署则使用负载均衡器地址。5.2 安装 k6k6 是 Grafana 开源的负载测试工具。使用你偏好的方式安装如 Homebrew、Chocolatey、Docker 容器等安装完成后通过k6 version验证可用性。本方案依赖 k6 的http、check、sleep模块与ramping-arrival-rate执行器均为 k6 内建能力无需额外插件。5.3 Schema 与版本写入 Schema将上文第 2 节的 Schema 通过 Permify 的 schema 写入接口/v1/tenants/{tenant_id}/schemas/write部署到目标租户获取 schema_version写入 Schema 后通过 schema 读取接口获取版本号参见 docs/api-reference/schema/list-schema.mdx替换占位符将k6test.js压测脚本与种子脚本中的schema-version替换为实际版本号。注意种子脚本与压测脚本必须指向同一 Schema 版本否则data/write与check的实体定义校验会不一致。5.4 执行顺序先运行种子脚本灌入 30 万实体量级的关系与属性数据再运行压测脚本观察 k6 汇总输出中的http_req_duration、checks、http_reqs等指标。6. 测试结果解读以下是官方在所述压测环境Permify 1.6.9 GKE Postgres 15 on Cloud SQL下执行压测后记录的结果Read 测试即check请求6.1 结果总览MetricValue/Statschecks100.00% (74614 out of 74614)data_received17 MB (168 kB/s)data_sent27 MB (268 kB/s)dropped_iterations272433 (2696.482348/s)http_req_blockedavg5.59µs min0s med2µs max2.08ms p(90)4µs p(95)5µshttp_req_connectingavg2.57µs min0s med0s max2.04ms p(90)0s p(950shttp_req_durationavg21.3ms min428µs med15.38ms max617.85ms p(90)45.7ms p(95)58.99msexpected_responseavg21.3ms min428µs med15.38ms max617.85ms p(90)45.7ms p(95)58.99mshttp_req_failed0.00% (0 out of 74614)http_req_receivingavg20.27µs min4µs med17µs max2.86ms p(90)37µs p(95)44µshttp_req_sendingavg11.16µs min1µs med8µs max2.51ms p(90)18µs p(95)22µshttp_req_tls_handshakingavg59.38µs min0s med0s max44.21ms p(90)0s p(95)0shttp_req_waitingavg21.27ms min399µs med15.35ms max617.83ms p(90)45.67ms p(95)58.96mshttp_reqs74614 (738.51308/s)iteration_durationavg1.02s min1s med1.01s max1.61s p(90)1.04s p(95)1.05siterations74614 (738.51308/s)vus114 (min14, max1000)vus_max1000 (min50, max1000)6.2 指标含义与观察要点checks 100.00%74614/74614、http_req_failed 0.00%全部 74614 个请求均返回了 200 状态码压测期间没有服务端错误说明在目标负载下 Permify 的处理正确性有保障http_req_duration avg21.3msp(95)58.99ms平均 21.3ms、95 分位约 59ms 的检查延迟。绝大部分请求耗时集中在 15ms中位数附近max617.85ms的尾部延迟来自峰值阶段http_req_waiting avg21.27ms服务端响应等待时间与总耗时几乎相等avg21.27ms vs 21.3ms而sending/receiving均为微秒级说明延迟几乎全部产生于服务端授权计算而非网络传输http_reqs 74614738.5/s这是实际完成的请求量。需要说明的是k6 的ramping-arrival-rate目标到达速率与sleep(1)叠加实际吞吐受迭代内部耗时与 VU 上限约束dropped_iterations 2724332696.48/s被丢弃的迭代数即在到达速率超出执行能力时 k6 主动放弃的迭代。结合http_reqs 738/s可推断峰值阶段的目标速率10000 RPS远超当前 VU 配置所能支撑的实际吞吐这与脚本maxVUs: 100而结果表中vus_max: 1000的配置差异相关说明压测配置在结果记录时已随环境调整vus 114min14, max1000实际运行的虚拟用户峰值达到 1000说明场景在峰值阶段确实拉满了 VU数据量接收 17 MB、发送 27 MB单请求请求体约 360 字节、响应体约 228 字节带宽占用极小。6.3 关键结论从这份官方记录可以得出在 GKE Postgres 15 环境下Permify 能够以100% 成功率、平均 21.3ms / p(95) ≈ 59ms的延迟稳定处理权限检查请求且延迟几乎全部来自服务端授权计算环节。该结果可作为团队评估 Permify 容量规划、限流阈值rate_limit与缓存配置permission.cache的参考基线不同硬件、副本数与数据规模下的表现建议按本文方法在自有环境复测验证。7. 注意事项与调优建议版本一致性种子脚本与压测脚本中的schema_version必须与实际部署的 Schema 版本一致否则data/write会因实体定义校验失败而中断参见 internal/servers/data_server.go限流与峰值的关系server.rate_limit即服务端每秒最大请求数令牌桶压测峰值不应明显超过该值否则请求会被限流拒绝见 internal/middleware/limiter.go如需更高吞吐先同步上调限流、Pod 副本与permission.concurrency_limit缓存对结果的影响permission.cachenumber_of_counters: 10000 / max_cost: 256MiB会让 10% ID 复用策略命中的热点检查显著加速评估冷数据场景时可适当降低reuseIdProbability预热必要性两段式预热100 RPS → 1000 RPS能让连接池与缓存就绪直接满负载启动会导致首轮迭代大量超时与丢包建议保留结果对比的可移植性本结果是官方特定环境下的基准不同集群机型、Postgres 实例规格与副本数会产生差异请以自有环境实测数据为准。【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 项目地址: https://gitcode.com/GitHub_Trending/pe/permify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表