news 2026/9/17 23:34:12

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 Engine(GKE)general-purpose 机型
数据库Postgres 15(部署于 Google Cloud SQL)
Helm 配置docs/performance-test/values.yaml

其中values.yaml保存了压测环境使用的默认配置(Helm values、资源设置等),也是理解压测调优的关键文件。值得注意的是,该文件中的镜像 tag 为v1.3.0,而 README 声明的压测版本为 1.6.9,实际使用时请以你部署的镜像 tag 为准。

1.1 压测环境关键配置解读(values.yaml)

values.yaml 中与压测表现直接相关的配置点如下:

  • 副本与弹性autoscaling.minReplicas: 5maxReplicas: 15,允许在 10000 RPS 峰值下自动扩容;
  • 资源配额resources.limits.cpu: 300m / memory: 600Mi,请求配额150m / 300Mi,说明压测目标是让小规格 Pod 在水平扩展下承担高负载;
  • 服务端口:HTTP 网关端口3476,gRPC 端口3478(与serve命令默认端口一致,见 pkg/cmd/serve.go);
  • 限流server.rate_limit: 10000,即服务端每秒最多处理 10000 个请求——这与压测目标 10000 RPS 峰值严格对齐,限流阈值刚好覆盖全量负载;
  • 缓存:schema 缓存number_of_counters: 1000 / max_cost: 24MiB,permission 缓存number_of_counters: 10000 / max_cost: 256MiB,其中 permission 缓存专门服务于check引擎,对压测延迟影响最大;
  • 并发控制permission.concurrency_limit: 1000,限制授权检查引擎的并发执行数;
  • 数据库:Postgres 引擎,读写分离 URI,max_connections: 1min_connections: 1(连接池),auto_migrate: truegarbage_collection开启(间隔 7200s、窗口 7200s、超时 5m),max_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 授权模型中的几类核心计算模式:

  1. 关系直接判断user.view = self,命中单条关系元组;
  2. ABAC 属性参与is_public or follower,boolean 属性与关系元组混合运算;
  3. 排除语义(EXCLUSION)... not blocked,对应check引擎的 exclusion 组合器;
  4. 跨实体关系引用owner.selfowner.follower,触发对user实体定义的递归解析;
  5. 递归权限组合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),其处理流程与压测数据质量直接相关:

  1. Schema 版本解析:若请求未携带metadata.schema_version,服务端会读取当前租户的 head version(HeadVersion);
  2. 去重:对元组与属性分别以"实体+关系/属性"为 key 建立 map 去重,重复写入会被跳过;
  3. 逐条校验:每条元组通过validation.ValidateTuple、每条属性通过validation.ValidateAttribute校验——校验依赖ReadEntityDefinition读取对应 schema 版本下的实体定义,所以必须确保写入的schema_version与已部署 Schema 匹配,否则批量写入会失败;
  4. 落库返回快照:校验通过后由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还是interactionview权限——interactionview会递归触发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.go):

func 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 安装 k6

k6 是 Grafana 开源的负载测试工具。使用你偏好的方式安装(如 Homebrew、Chocolatey、Docker 容器等),安装完成后通过k6 version验证可用性。本方案依赖 k6 的httpchecksleep模块与ramping-arrival-rate执行器,均为 k6 内建能力,无需额外插件。

5.3 Schema 与版本

  1. 写入 Schema:将上文第 2 节的 Schema 通过 Permify 的 schema 写入接口(/v1/tenants/{tenant_id}/schemas/write)部署到目标租户;
  2. 获取 schema_version:写入 Schema 后,通过 schema 读取接口获取版本号(参见 docs/api-reference/schema/list-schema.mdx);
  3. 替换占位符:将k6test.js(压测脚本)与种子脚本中的<schema-version>替换为实际版本号。注意:种子脚本与压测脚本必须指向同一 Schema 版本,否则data/writecheck的实体定义校验会不一致。

5.4 执行顺序

  1. 先运行种子脚本灌入 30 万实体量级的关系与属性数据;
  2. 再运行压测脚本,观察 k6 汇总输出中的http_req_durationcheckshttp_reqs等指标。

6. 测试结果解读

以下是官方在所述压测环境(Permify 1.6.9 + GKE + Postgres 15 on Cloud SQL)下执行压测后记录的结果(Read 测试,即check请求):

6.1 结果总览

MetricValue/Stats
checks100.00% (74614 out of 74614)
data_received17 MB (168 kB/s)
data_sent27 MB (268 kB/s)
dropped_iterations272433 (2696.482348/s)
http_req_blockedavg=5.59µs min=0s med=2µs max=2.08ms p(90)=4µs p(95)=5µs
http_req_connectingavg=2.57µs min=0s med=0s max=2.04ms p(90)=0s p(95=0s
http_req_durationavg=21.3ms min=428µs med=15.38ms max=617.85ms p(90)=45.7ms p(95)=58.99ms
expected_responseavg=21.3ms min=428µs med=15.38ms max=617.85ms p(90)=45.7ms p(95)=58.99ms
http_req_failed0.00% (0 out of 74614)
http_req_receivingavg=20.27µs min=4µs med=17µs max=2.86ms p(90)=37µs p(95)=44µs
http_req_sendingavg=11.16µs min=1µs med=8µs max=2.51ms p(90)=18µs p(95)=22µs
http_req_tls_handshakingavg=59.38µs min=0s med=0s max=44.21ms p(90)=0s p(95)=0s
http_req_waitingavg=21.27ms min=399µs med=15.35ms max=617.83ms p(90)=45.67ms p(95)=58.96ms
http_reqs74614 (738.51308/s)
iteration_durationavg=1.02s min=1s med=1.01s max=1.61s p(90)=1.04s p(95)=1.05s
iterations74614 (738.51308/s)
vus114 (min=14, max=1000)
vus_max1000 (min=50, max=1000)

6.2 指标含义与观察要点

  • checks 100.00%(74614/74614)、http_req_failed 0.00%:全部 74614 个请求均返回了 200 状态码,压测期间没有服务端错误,说明在目标负载下 Permify 的处理正确性有保障;
  • http_req_duration avg=21.3ms,p(95)=58.99ms:平均 21.3ms、95 分位约 59ms 的检查延迟。绝大部分请求耗时集中在 15ms(中位数)附近,max=617.85ms的尾部延迟来自峰值阶段;
  • http_req_waiting avg=21.27ms:服务端响应等待时间与总耗时几乎相等(avg=21.27ms vs 21.3ms),而sending/receiving均为微秒级,说明延迟几乎全部产生于服务端授权计算而非网络传输;
  • http_reqs = 74614(738.5/s):这是实际完成的请求量。需要说明的是,k6 的ramping-arrival-rate目标到达速率与sleep(1)叠加,实际吞吐受迭代内部耗时与 VU 上限约束;
  • dropped_iterations 272433(2696.48/s):被丢弃的迭代数,即在到达速率超出执行能力时 k6 主动放弃的迭代。结合http_reqs 738/s可推断,峰值阶段的目标速率(10000 RPS)远超当前 VU 配置所能支撑的实际吞吐(这与脚本maxVUs: 100而结果表中vus_max: 1000的配置差异相关,说明压测配置在结果记录时已随环境调整);
  • vus 114(min=14, max=1000):实际运行的虚拟用户峰值达到 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 23:34:00

Control4简单编程实战:事件驱动逻辑与调试技巧

简介&#xff1a;这是一份面向智能家居安装调试人员及初学者的Control4编程入门文档&#xff0c;聚焦Composer软件下的基础操作流程&#xff0c;帮助读者快速掌握从环境搭建到媒体播放配置的完整链路。资源为单个doc文档&#xff0c;体积约802KB&#xff0c;内容紧扣实际调试场…

作者头像 李华