Security Policy Benchmark
Local benchmark for measuring Row Access Policy (RAP) and Data Mask Policy
performance overhead in Databend.
RAP vs WHERE : Overhead of row access policy (auto prewhere pushdown) compared
to an equivalent hand-written WHERE clause.
Mask vs Function : Overhead of masking policy (auto function replacement) compared
to an equivalent hand-written function call in SELECT.
Planner Cache : Cold vs warm plan cache latency with and without security policies.
Against a running Databend instance
# Default: 5M rows, all complexity levels, 5 bench rounds
python3 scripts/security_policy/security_policy_bench.py \
--dsn " databend://root@127.0.0.1:8000/default?sslmode=disable" \
--output tmp/security_policy_bench_result.json
# Quick run: single complexity, fewer rounds
python3 scripts/security_policy/security_policy_bench.py \
--complexity simple --bench-rounds 3 --output tmp/quick.json
# Dry run: print generated SQL without executing
python3 scripts/security_policy/security_policy_bench.py --dry-run
With a specific binary (auto start/stop services)
python3 scripts/security_policy/run_security_policy_bench_with_bin.py \
--query-bin target/release/databend-query \
--output tmp/result.json
Cross-version A/B comparison
python3 scripts/security_policy/run_security_policy_bench_with_bin.py \
--query-bin target/release/databend-query \
--compare-bin /path/to/other/databend-query \
--output tmp/result.json
This runs the benchmark on both binaries and outputs a diff report. Regressions
(>5% slower) are flagged.
Argument
Default
Description
--dsn
env BENDSQL_DSN
bendsql connection string
--rows
5,000,000
Total rows to insert
--rows-per-batch
1,000,000
Rows per INSERT batch
--rows-per-block
10,000
ROW_PER_BLOCK table option
--blocks-per-segment
10
BLOCK_PER_SEGMENT table option
--complexity
all
RAP/mask complexity: simple, range, complex, all
--pattern
all
Query pattern filter
--warmup-rounds
2
Warmup iterations before measurement
--bench-rounds
5
Measurement iterations
--output
none
JSON output path
--keep
false
Don't drop database before run
--drop-after
false
Drop database after run
--dry-run
false
Print SQL without executing
run_security_policy_bench_with_bin.py
Argument
Default
Description
--query-bin
required
Path to databend-query binary
--meta-bin
auto-detect
Path to databend-meta binary
--compare-bin
none
Second binary for A/B comparison
--bench-arg
none
Extra args passed to bench script (repeatable)
--no-stop
false
Leave services running after bench
Primary metrics (per query)
duration_ms: Best-of-N wall time (ms)
duration_avg_ms: Average wall time across rounds
all_ms: All individual round timings
Pruning stats (RAP queries)
segments_before/after: Segment range pruning
blocks_before/after: Block range pruning
*_pruned_pct: Percentage pruned
rap_vs_where.overhead_pct: Policy overhead relative to manual WHERE
mask_vs_func.overhead_pct: Policy overhead relative to manual function
CREATE TABLE bench_security (
id INT NOT NULL ,
tenant_id INT NOT NULL ,
email VARCHAR NOT NULL ,
amount DECIMAL (18 ,2 ) NOT NULL ,
status VARCHAR NOT NULL ,
created_at TIMESTAMP NOT NULL
) CLUSTER BY (created_at);
Clustered by time (realistic production pattern)
100 distinct tenant_id values scattered across all blocks
RAP predicates on tenant_id force prewhere filtering (no cluster pruning shortcut)
Level
RAP predicate
Mask expression
simple
tenant_id = 1
'***'
range
tenant_id BETWEEN 10 AND 30
CONCAT(LEFT(val,3), '***@***.com')
complex
tenant_id % 10 = 0 AND status = 'active'
CASE WHEN ... END