GrantForge

    Architecture

    Performance and benchmarks

    Performance targets at million-account scale, the benchmark data and methodology, and how to run it locally.

    GrantForge's performance targets address a large organization: one million accounts, ten thousand roles, one hundred thousand resources. The system benchmark runs nightly on PostgreSQL 17, and the build fails if any metric exceeds its ceiling.

    Targets

    Metric Meaning Ceiling
    authz.snapshot.hit A cached authorization snapshot (used on every API call) p99 1 ms
    authz.snapshot.build First computation of an account's console snapshot p95 50 ms
    authz.snapshot.app Computing a snapshot in an application with a large resource tree p95 50 ms
    api.users.page One page of the user list (within the first 500 pages) p95 200 ms
    api.users.search Searching users by name p95 200 ms
    api.groups.page One page of the user group list p95 200 ms
    api.roles.list The role list (unpaginated) p95 200 ms
    api.me.authorization The current account's permissions p95 200 ms
    api.member.groups An account with a single page permission accessing a protected list p95 200 ms
    jmh.derivation.* Preparing the derivation for a large resource tree; deriving twenty grants (JMH) avg 50 / 5 ms

    Ceilings may only be tightened; relaxing any of them requires a documented decision.

    Data

    The benchmark starts the real server, completes initialization, and then writes data through the application's own entities:

    • 100 departments, one user group per thousand accounts, 100 positions;
    • Each account belongs to one department and one user group; one in ten accounts holds a position, and one in twenty accounts has directly assigned roles;
    • One in ten roles inherits from one of the first hundred roles; each user group gets three roles, each department two, each position one; each role is granted five console pages;
    • One application: one hundred pages under several modules, nine operations per page; one in ten roles is granted twenty of those pages and operations.

    Each metric is warmed up and then measured call by call.

    Running it locally

    bash script/ci/perf_benchmark.sh full            # 目标规模,postgres:17(需要 Docker),检查上限
    bash script/ci/perf_benchmark.sh smoke           # H2 上的小规模,只确认基准能运行
    bash script/ci/perf_benchmark.sh full mysql:8.4  # 其他数据库

    The report is written to perf/target/perf-report.json, and a table is also printed to the log. Extra options can be passed with PERF_OPTS:

    Option Default Meaning
    perf.database postgres:17 h2 or <engine>:<version>
    perf.users / perf.roles / perf.resources 1000000 / 10000 / 100000 Data volume
    perf.samples / perf.warmup 1000 / 200 Measurements and warmup iterations per metric
    perf.jmh true Also run the JMH benchmarks
    perf.enforce true Exit with status 1 when a ceiling is exceeded

    Why it is fast

    • Authorization snapshots are cached per account and invalidated wholesale by catalog and tenant versions; a hit is a single in-memory read.
    • Derivation happens in memory: the resource tree, inheritance relations, and assignments are preloaded into compact structures, avoiding per-item queries.
    • List endpoints are always paginated, and sorting and filtering land on indexed columns; lazy associations load in batches of 64, and writes commit in batches of 50 (IDs are generated by the application, so batch inserts are possible).

    Edit this page on GitHub