Skip to content

Commit 6225490

Browse files
authored
1 parent 76bb468 commit 6225490

7 files changed

Lines changed: 641 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,6 @@ package-lock.json
6565
*.pyc
6666
__pycache__
6767
.venv
68+
69+
# Performance profiles written by `samply record`
70+
profile.json.gz

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,34 @@ check-schema-wasm-package: build-schema-wasm
139139
NODE=$(shell which node) \
140140
./prisma-schema-wasm/scripts/check.sh
141141

142+
######################
143+
# Benchmark commands #
144+
######################
145+
146+
# Run query compiler benchmarks
147+
bench-qc:
148+
cargo bench -p query-compiler --profile profiling
149+
150+
# Run query graph building benchmarks
151+
bench-qc-graph:
152+
cargo bench -p core-tests --profile profiling --bench query_graph_bench
153+
154+
# Run schema building benchmarks
155+
bench-schema:
156+
cargo bench -p schema --profile profiling --bench schema_builder_bench
157+
158+
# Save benchmark baseline (usage: make bench-baseline NAME=main)
159+
bench-qc-baseline:
160+
cargo bench -p query-compiler --profile profiling -- --save-baseline $(NAME)
161+
162+
# Compare against baseline (usage: make bench-compare NAME=main)
163+
bench-qc-compare:
164+
cargo bench -p query-compiler --profile profiling -- --baseline $(NAME)
165+
166+
# Run profile_query example for profiling
167+
profile-qc:
168+
cargo run -p query-compiler --example profile_query --profile profiling
169+
142170
###########################
143171
# Database setup commands #
144172
###########################

query-compiler/query-compiler/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,21 @@ pretty = { workspace = true, features = ["termcolor"] }
2020
indexmap = { workspace = true, features = ["serde"] }
2121

2222
[dev-dependencies]
23+
codspeed-criterion-compat.workspace = true
2324
insta = { workspace = true, features = ["glob"] }
24-
request-handlers.workspace = true
25+
request-handlers = { workspace = true, features = ["all"] }
26+
schema.workspace = true
2527
# pull all connectors for testing
2628
psl = { workspace = true, features = ["sqlite", "mysql", "postgresql", "mssql", "cockroachdb"] }
2729
quaint = { workspace = true, features = ["sqlite", "mysql", "postgresql", "mssql"] }
30+
sql-query-builder = { workspace = true, features = ["relation_joins"] }
31+
32+
[[example]]
33+
name = "profile_query"
34+
35+
[[bench]]
36+
name = "compilation_bench"
37+
harness = false
2838

2939
[features]
3040
default = ["all"]
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! End-to-end compilation benchmarks for the query compiler.
2+
//!
3+
//! This benchmark uses the same schema and query fixtures as the unit tests
4+
//! in `tests/data/` to measure full compilation pipeline performance.
5+
//!
6+
//! New benchmarks are automatically discovered when new query JSON files
7+
//! are added to the `tests/data/` directory.
8+
//!
9+
//! ## Running benchmarks
10+
//!
11+
//! ```bash
12+
//! cargo bench -p query-compiler
13+
//! ```
14+
//!
15+
//! ## Filtering benchmarks
16+
//!
17+
//! ```bash
18+
//! cargo bench -p query-compiler -- "create"
19+
//! cargo bench -p query-compiler -- "query-m2o"
20+
//! ```
21+
22+
use codspeed_criterion_compat::{Criterion, black_box, criterion_group, criterion_main};
23+
use itertools::Itertools;
24+
use quaint::prelude::{ConnectionInfo, ExternalConnectionInfo, SqlFamily};
25+
use query_compiler::compile;
26+
use request_handlers::{JsonBody, JsonSingleQuery, RequestBody};
27+
use schema::QuerySchema;
28+
use std::sync::Arc;
29+
use std::{fs, path::PathBuf};
30+
31+
fn get_test_data_dir() -> PathBuf {
32+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data")
33+
}
34+
35+
struct BenchContext {
36+
query_schema: Arc<QuerySchema>,
37+
connection_info: ConnectionInfo,
38+
}
39+
40+
impl BenchContext {
41+
fn new() -> Self {
42+
let data_dir = get_test_data_dir();
43+
let schema_path = data_dir.join("schema.prisma");
44+
let schema_str = fs::read_to_string(&schema_path)
45+
.unwrap_or_else(|e| panic!("Failed to read {}: {}", schema_path.display(), e));
46+
47+
let validated_schema = psl::parse_schema_without_extensions(&schema_str).unwrap();
48+
let query_schema = Arc::new(schema::build(Arc::new(validated_schema), true));
49+
let connection_info = ConnectionInfo::External(ExternalConnectionInfo::new(
50+
SqlFamily::Postgres,
51+
Some("public".to_string()),
52+
None,
53+
true,
54+
));
55+
Self {
56+
query_schema,
57+
connection_info,
58+
}
59+
}
60+
61+
fn compile(&self, query_json: &str) {
62+
let query: JsonSingleQuery = serde_json::from_str(query_json).unwrap();
63+
let request = RequestBody::Json(JsonBody::Single(query));
64+
let doc = request.into_doc(&self.query_schema).unwrap();
65+
66+
let query_core::QueryDocument::Single(operation) = doc else {
67+
panic!("expected single query");
68+
};
69+
70+
let mut expr = compile(&self.query_schema, operation, &self.connection_info).unwrap();
71+
expr.simplify();
72+
black_box(expr);
73+
}
74+
}
75+
76+
fn discover_query_files() -> impl IntoIterator<Item = (String, String)> {
77+
let data_dir = get_test_data_dir();
78+
79+
fs::read_dir(&data_dir)
80+
.expect("failed to read data directory")
81+
.map(|entry| entry.expect("failed to read directory entry").path())
82+
.filter(|path| path.extension().is_some_and(|ext| ext == "json"))
83+
.sorted()
84+
.map(|path| {
85+
let name = path
86+
.file_stem()
87+
.expect("file should have a stem")
88+
.to_string_lossy()
89+
.into_owned();
90+
91+
let content =
92+
fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
93+
94+
(name, content)
95+
})
96+
}
97+
98+
fn compilation_benchmarks(c: &mut Criterion) {
99+
let ctx = BenchContext::new();
100+
let queries = discover_query_files();
101+
102+
let mut group = c.benchmark_group("compile");
103+
104+
for (name, query_json) in queries {
105+
group.bench_function(name, |b| {
106+
b.iter(|| ctx.compile(&query_json));
107+
});
108+
}
109+
110+
group.finish();
111+
}
112+
113+
criterion_group!(benches, compilation_benchmarks);
114+
criterion_main!(benches);
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Query Compiler Benchmarking and Profiling Guide
2+
3+
This document describes how to run benchmarks and profile the query compiler to identify performance bottlenecks.
4+
5+
## Quick Start
6+
7+
```bash
8+
# Run all query-compiler benchmarks
9+
cargo bench -p query-compiler
10+
11+
# Or use the Makefile target
12+
make bench-qc
13+
14+
# Run benchmarks matching a pattern
15+
cargo bench -p query-compiler -- "create"
16+
cargo bench -p query-compiler -- "query-m2o"
17+
```
18+
19+
## How Benchmarks Work
20+
21+
The compilation benchmarks automatically discover all `.json` query files in `tests/data/` and create a benchmark for each one. This means:
22+
23+
- **No code changes needed** when adding new test queries
24+
- Benchmarks stay in sync with unit tests
25+
- Each benchmark measures full end-to-end compilation (JSON → Expression)
26+
27+
The benchmarks use the same schema (`tests/data/schema.prisma`) as the unit tests.
28+
29+
## Running Benchmarks
30+
31+
### Basic Usage
32+
33+
```bash
34+
# Run all benchmarks
35+
cargo bench -p query-compiler
36+
37+
# Run benchmarks matching a pattern
38+
cargo bench -p query-compiler -- "compile/create"
39+
cargo bench -p query-compiler -- "compile/query-m2o"
40+
41+
# Save baseline for comparison
42+
cargo bench -p query-compiler -- --save-baseline main
43+
44+
# Compare against baseline
45+
cargo bench -p query-compiler -- --baseline main
46+
```
47+
48+
### Makefile Targets
49+
50+
```bash
51+
make bench-qc # Run query compiler benchmarks
52+
make bench-qc-graph # Run query graph benchmarks
53+
make bench-schema # Run schema building benchmarks
54+
make bench-baseline NAME=main # Save baseline
55+
make bench-compare NAME=main # Compare against baseline
56+
make profile-qc # Run profiling example
57+
```
58+
59+
### Benchmark Options
60+
61+
```bash
62+
# Run with more iterations for accuracy
63+
cargo bench -p query-compiler -- --sample-size 200
64+
65+
# Run for a specific duration
66+
cargo bench -p query-compiler -- --measurement-time 30
67+
68+
# Skip warmup (useful for debugging)
69+
cargo bench -p query-compiler -- --warm-up-time 0
70+
71+
# List all available benchmarks
72+
cargo bench -p query-compiler -- --list
73+
```
74+
75+
## Adding New Benchmarks
76+
77+
Simply add a new `.json` query file to `tests/data/`. The benchmark will be automatically discovered on the next run.
78+
79+
For example, adding `tests/data/my-new-query.json` will create a benchmark named `compile/my-new-query`.
80+
81+
## Profiling
82+
83+
### Using the Profiling Example
84+
85+
The `profile_query` example is designed for use with profilers:
86+
87+
```bash
88+
# Run with default settings (10,000 iterations)
89+
cargo run -p query-compiler --example profile_query --profile profiling
90+
91+
# Customize via environment variables
92+
PROFILE_ITERATIONS=50000 PROFILE_QUERY=nested cargo run -p query-compiler --example profile_query --profile profiling
93+
```
94+
95+
Environment variables:
96+
- `PROFILE_ITERATIONS`: Number of iterations (default: 10000)
97+
- `PROFILE_QUERY`: Which query to profile (`simple`, `nested`, `mutation`, or `all`)
98+
- `PROFILE_WARMUP`: Number of warmup iterations (default: 100)
99+
100+
### Using samply (Recommended, Cross-platform)
101+
102+
```bash
103+
cargo install samply
104+
105+
# Profile the example
106+
samply record cargo run -p query-compiler --example profile_query --profiling
107+
108+
# Opens Firefox Profiler with results
109+
```
110+
111+
### Using Instruments.app (macOS)
112+
113+
```bash
114+
cargo build -p query-compiler --example profile_query --profile profiling
115+
116+
xcrun xctrace record --template 'Time Profiler' --launch -- \
117+
./target/profiling/examples/profile_query
118+
```
119+
120+
### Using perf (Linux)
121+
122+
```bash
123+
cargo build -p query-compiler --example profile_query --profile profiling
124+
125+
perf record -g ./target/profiling/examples/profile_query
126+
perf report
127+
```
128+
129+
### Using flamegraph
130+
131+
```bash
132+
# macOS
133+
brew install flamegraph
134+
135+
# Linux
136+
cargo install flamegraph
137+
138+
# Generate flamegraph
139+
cargo flamegraph -p query-compiler --example profile_query
140+
```
141+
142+
## Interpreting Results
143+
144+
### Criterion Output
145+
146+
```
147+
compile/query-m2o time: [143.02 µs 156.65 µs 173.14 µs]
148+
change: [-2.12% -0.57% +1.01%] (p = 0.42 > 0.05)
149+
No change in performance detected.
150+
```
151+
152+
- **time**: [lower bound, estimate, upper bound] at 95% confidence
153+
- **change**: Comparison to previous run or baseline
154+
- **p-value**: Statistical significance (< 0.05 means significant change)
155+
156+
### Performance Regression Detection
157+
158+
- Changes > 5% warrant investigation
159+
- Changes > 10% are likely regressions
160+
- Always verify with multiple runs
161+
162+
## Other Benchmark Suites
163+
164+
- `query-compiler/core-tests/benches/query_graph_bench.rs` - Query graph building
165+
- `query-compiler/schema/benches/schema_builder_bench.rs` - Schema building
166+
167+
## Troubleshooting
168+
169+
### Benchmarks are too slow
170+
171+
```bash
172+
# Use fewer samples for quick runs
173+
cargo bench -p query-compiler -- --sample-size 10
174+
175+
# Filter to specific benchmarks
176+
cargo bench -p query-compiler -- "compile/query-m2o"
177+
```
178+
179+
### High variance in results
180+
181+
- Close other applications
182+
- Use `--warm-up-time 5` for longer warmup
183+
- Use `--sample-size 500` for more samples
184+
185+
### Missing debug symbols in profiles
186+
187+
Use the `profiling` profile defined in the workspace `Cargo.toml`:
188+
189+
```bash
190+
cargo build -p query-compiler --profile profiling
191+
```

0 commit comments

Comments
 (0)