Files
Oxicloud/tests/load/scenarios/share_cascade_rebac.js
T
Edouard Vanbelle 0ef4c624c5 chore(load): start implementation of load tests
initial test from Ed's nuc:

    metric                                          pctl  baseline    current     delta    status
    -----------------------------------------------------------------------------------------------
    folder_cascade.list_depth1                      p50   0.3ms       0.3ms       -6.3%    ok
    folder_cascade.list_depth1                      p95   2.3ms       0.5ms       -75.7%   ok
    folder_cascade.list_depth1                      p99   4.7ms       2.5ms       -48.1%   ok
    folder_cascade.list_depth4                      p50   0.4ms       0.3ms       -10.0%   ok
    folder_cascade.list_depth4                      p95   0.9ms       0.6ms       -31.2%   ok
    folder_cascade.list_depth4                      p99   2.4ms       1.0ms       -56.7%   ok
    folder_cascade.list_depth8                      p50   0.3ms       0.3ms       -8.8%    ok
    folder_cascade.list_depth8                      p95   0.6ms       0.5ms       -22.2%   ok
    folder_cascade.list_depth8                      p99   1.9ms       0.5ms       -71.5%   ok
    folder_cascade.list_depth_deep                  p50   0.3ms       0.3ms       -5.0%    ok
    folder_cascade.list_depth_deep                  p95   0.6ms       0.5ms       -18.6%   ok
    folder_cascade.list_depth_deep                  p99   2.0ms       0.7ms       -67.2%   ok
    share_cascade_rebac.list_grants                 p50   0.4ms       0.3ms       -27.6%   ok
    share_cascade_rebac.list_grants                 p95   1.2ms       0.5ms       -57.0%   ok
    share_cascade_rebac.list_grants                 p99   1.7ms       1.1ms       -36.7%   ok
    share_cascade_rebac.fetch_as_grantee_depth1     p50   0.5ms       0.5ms       -11.1%   ok
    share_cascade_rebac.fetch_as_grantee_depth1     p95   1.1ms       0.7ms       -41.2%   ok
    share_cascade_rebac.fetch_as_grantee_depth1     p99   3.0ms       1.3ms       -58.3%   ok
    share_cascade_rebac.fetch_as_grantee_depth4     p50   0.5ms       0.5ms       -13.7%   ok
    share_cascade_rebac.fetch_as_grantee_depth4     p95   1.4ms       0.7ms       -50.5%   ok
    share_cascade_rebac.fetch_as_grantee_depth4     p99   2.2ms       1.1ms       -49.3%   ok
    share_cascade_rebac.fetch_as_grantee_depth8     p50   0.5ms       0.4ms       -16.0%   ok
    share_cascade_rebac.fetch_as_grantee_depth8     p95   0.9ms       0.7ms       -26.1%   ok
    share_cascade_rebac.fetch_as_grantee_depth8     p99   1.5ms       0.9ms       -40.1%   ok
    share_cascade_rebac.fetch_as_grantee_depth_deep p50   0.5ms       0.4ms       -17.3%   ok
    share_cascade_rebac.fetch_as_grantee_depth_deep p95   1.0ms       0.7ms       -31.2%   ok
    share_cascade_rebac.fetch_as_grantee_depth_deep p99   1.6ms       0.8ms       -49.4%   ok
    subject_group_nested.fetch_as_member_depth1     p50   0.5ms       0.4ms       -8.4%    ok
    subject_group_nested.fetch_as_member_depth1     p95   0.6ms       0.6ms       -10.2%   ok
    subject_group_nested.fetch_as_member_depth1     p99   1.4ms       0.6ms       -55.2%   ok
    subject_group_nested.fetch_as_member_depth4     p50   0.5ms       0.5ms       -7.9%    ok
    subject_group_nested.fetch_as_member_depth4     p95   0.6ms       0.6ms       -4.0%    ok
    subject_group_nested.fetch_as_member_depth4     p99   0.7ms       0.6ms       -2.2%    ok
    subject_group_nested.fetch_as_member_depth8     p50   0.5ms       0.4ms       -8.9%    ok
    subject_group_nested.fetch_as_member_depth8     p95   0.5ms       0.6ms       +7.1%    ok
    subject_group_nested.fetch_as_member_depth8     p99   0.6ms       0.7ms       +10.3%   ok
    subject_group_nested.fetch_as_member_depth_deep p50   0.5ms       0.4ms       -7.6%    ok
    subject_group_nested.fetch_as_member_depth_deep p95   0.6ms       0.5ms       -11.9%   ok
    subject_group_nested.fetch_as_member_depth_deep p99   0.6ms       0.7ms       +8.9%    ok
2026-06-18 09:38:25 +02:00

85 lines
3.5 KiB
JavaScript

// share_cascade_rebac.js — measures ReBAC AuthZ cascade via a direct user
// grant. The seeder grants `read` on shared_subtree.root (depth 0) to the
// grantee user; this scenario times how long the grantee takes to fetch
// folders at varying depths inside that subtree.
//
// The AuthZ recursive-CTE in `pg_acl_engine` walks up the ancestor chain
// from the requested folder until it finds a matching grant (or runs out
// of ancestors). The deeper the requested folder, the more ancestors the
// CTE has to traverse before hitting the grant on the root:
//
// fetch_at_grant_root → 0 ancestors walked (grant is right here)
// fetch_as_grantee_depth4 → 4 ancestors walked
// fetch_as_grantee_depth8 → 8 ancestors walked
// fetch_as_grantee_depth_deep → `load_depth` ancestors walked
//
// A regression in AuthZ cost should show up as the cascade-depth curve
// flattening or steepening — that's the value of intermediate samples.
import { check } from 'k6';
import http from 'k6/http';
import { BASE, authParams } from '../lib/http.js';
import { login } from '../lib/auth.js';
import { thresholdsFromBaseline, loadManifest } from '../lib/metrics.js';
const manifest = loadManifest();
export const options = {
vus: 1,
// See folder_cascade.js for the iteration-count rationale: N=100 makes
// p99 a real percentile instead of the worst-of-the-batch.
iterations: 100,
thresholds: thresholdsFromBaseline('share_cascade_rebac'),
};
export function setup() {
const adminToken = login(manifest.admin.username, manifest.admin.password);
const granteeToken = login(manifest.grantee.username, manifest.grantee.password);
return { adminToken, granteeToken };
}
export default function (data) {
const { adminToken, granteeToken } = data;
const t = manifest.shared_subtree;
// List grants on the granted folder (admin only).
const grantsRes = http.get(
`${BASE}/api/grants?resource_type=folder&resource_id=${t.root}`,
authParams(adminToken, 'share_cascade_rebac.list_grants'),
);
check(grantsRes, { 'list grants 200': (r) => r.status === 200 });
// Baseline: grantee fetches the folder the grant is directly on. AuthZ
// finds the matching grant on the first row of the CTE; zero ancestors
// walked. This metric measures the constant overhead of an authorized
// request — moves to the right of this metric is "cascade cost."
const d1 = http.get(
`${BASE}/api/folders/${t.root}/resources?resource_types=folder`,
authParams(granteeToken, 'share_cascade_rebac.fetch_as_grantee_depth1'),
);
check(d1, { 'fetch root 200': (r) => r.status === 200 });
// Grantee fetches a mid-tree folder. AuthZ walks 4 ancestors before
// hitting the grant.
const d4 = http.get(
`${BASE}/api/folders/${t.depth4}/resources?resource_types=folder`,
authParams(granteeToken, 'share_cascade_rebac.fetch_as_grantee_depth4'),
);
check(d4, { 'fetch depth4 200': (r) => r.status === 200 });
// Grantee fetches a folder 8 levels under the granted root. AuthZ
// walks 8 ancestors.
const d8 = http.get(
`${BASE}/api/folders/${t.depth8}/resources?resource_types=folder`,
authParams(granteeToken, 'share_cascade_rebac.fetch_as_grantee_depth8'),
);
check(d8, { 'fetch depth8 200': (r) => r.status === 200 });
// Grantee fetches the deepest descendant — full-length cascade.
const dD = http.get(
`${BASE}/api/folders/${t.deepest}/resources?resource_types=folder`,
authParams(granteeToken, 'share_cascade_rebac.fetch_as_grantee_depth_deep'),
);
check(dD, { 'fetch deepest 200': (r) => r.status === 200 });
}