Skip to content

Commit abbe4c2

Browse files
authored
Add somewhat complex benchmark (#4034)
* Add stadium benchmark for testing complex nested queries This commit adds a comprehensive benchmark test for evaluating Strawberry's performance with large, deeply nested GraphQL queries. The benchmark simulates a stadium seating system with approximately 45,000-90,000 seat objects across multiple stands. Changes: - Add test_stadium.py: Benchmark test with parameterized seat counts - Add stadium.graphql: GraphQL query for the stadium benchmark - Add README.md: Documentation for running benchmarks and generating flame graphs The benchmark tests two configurations: - 250 seats per row (~45,000 total seats) - 500 seats per row (~90,000 total seats) The README includes detailed instructions for: - Running benchmarks with pytest-codspeed - Generating flame graphs using py-spy, Austin, and cProfile - Interpreting flame graph results - Adding new benchmarks to the test suite 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci
1 parent 80503ff commit abbe4c2

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
query StadiumQuery($seatsPerRow: Int!) {
2+
stadium(seatsPerRow: $seatsPerRow) {
3+
city
4+
country
5+
name
6+
stands {
7+
sectionType
8+
seats {
9+
labels
10+
x
11+
y
12+
}
13+
priceCategory
14+
name
15+
}
16+
}
17+
}

tests/benchmarks/test_stadium.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
"""Benchmark for a complex nested query with a large stadium dataset.
2+
3+
This benchmark tests Strawberry's performance when dealing with deeply nested
4+
objects and large result sets. The stadium query generates approximately 50,000
5+
seat objects across multiple stands, each with multiple labels and coordinates.
6+
"""
7+
8+
import asyncio
9+
from pathlib import Path
10+
11+
import pytest
12+
from pytest_codspeed.plugin import BenchmarkFixture
13+
14+
import strawberry
15+
16+
17+
@strawberry.type
18+
class Seat:
19+
x: int
20+
y: int
21+
labels: list[str]
22+
23+
24+
@strawberry.type
25+
class Stand:
26+
name: str
27+
seats: list[Seat]
28+
section_type: str
29+
price_category: str
30+
31+
32+
@strawberry.type
33+
class Stadium:
34+
name: str
35+
city: str
36+
country: str
37+
stands: list[Stand]
38+
39+
40+
def generate_seats_for_stand(
41+
stand_name: str,
42+
rows: int,
43+
seats_per_row: int,
44+
x_offset: int,
45+
y_offset: int,
46+
section_type: str,
47+
) -> list[Seat]:
48+
"""Generate seats for a stand with proper coordinates and labels."""
49+
seats = []
50+
51+
for row in range(rows):
52+
for seat_num in range(seats_per_row):
53+
x = x_offset + seat_num
54+
y = y_offset + row
55+
56+
row_label = (
57+
chr(65 + row)
58+
if row < 26
59+
else f"{chr(65 + row // 26)}{chr(65 + row % 26)}"
60+
)
61+
62+
labels = [
63+
stand_name,
64+
f"Row-{row_label}",
65+
f"Seat-{seat_num + 1}",
66+
section_type,
67+
f"Block-{(row // 5) + 1}",
68+
]
69+
70+
seats.append(Seat(x=x, y=y, labels=labels))
71+
72+
return seats
73+
74+
75+
def create_stadium(seats_per_row: int = 250) -> Stadium:
76+
"""Create a stadium with a configurable number of seats per row.
77+
78+
Default configuration (250 seats/row) creates approximately 50,000 seats:
79+
- North Stand: 12,500 seats (50 rows × 250 seats)
80+
- South Stand: 12,500 seats (50 rows × 250 seats)
81+
- East Stand: 10,000 seats (40 rows × 250 seats)
82+
- West Stand: 10,000 seats (40 rows × 250 seats)
83+
"""
84+
stands = []
85+
86+
# North Stand
87+
north_stand_seats = generate_seats_for_stand(
88+
stand_name="North-Stand",
89+
rows=50,
90+
seats_per_row=seats_per_row,
91+
x_offset=0,
92+
y_offset=0,
93+
section_type="Standard",
94+
)
95+
stands.append(
96+
Stand(
97+
name="North Stand",
98+
seats=north_stand_seats,
99+
section_type="Standard",
100+
price_category="Bronze",
101+
)
102+
)
103+
104+
# South Stand
105+
south_stand_seats = generate_seats_for_stand(
106+
stand_name="South-Stand",
107+
rows=50,
108+
seats_per_row=seats_per_row,
109+
x_offset=0,
110+
y_offset=100,
111+
section_type="Standard",
112+
)
113+
stands.append(
114+
Stand(
115+
name="South Stand",
116+
seats=south_stand_seats,
117+
section_type="Standard",
118+
price_category="Bronze",
119+
)
120+
)
121+
122+
# East Stand
123+
east_stand_seats = generate_seats_for_stand(
124+
stand_name="East-Stand",
125+
rows=40,
126+
seats_per_row=seats_per_row,
127+
x_offset=300,
128+
y_offset=20,
129+
section_type="Premium",
130+
)
131+
stands.append(
132+
Stand(
133+
name="East Stand",
134+
seats=east_stand_seats,
135+
section_type="Premium",
136+
price_category="Gold",
137+
)
138+
)
139+
140+
# West Stand
141+
west_stand_seats = generate_seats_for_stand(
142+
stand_name="West-Stand",
143+
rows=40,
144+
seats_per_row=seats_per_row,
145+
x_offset=-300,
146+
y_offset=20,
147+
section_type="Premium",
148+
)
149+
stands.append(
150+
Stand(
151+
name="West Stand",
152+
seats=west_stand_seats,
153+
section_type="Premium",
154+
price_category="Gold",
155+
)
156+
)
157+
158+
return Stadium(
159+
name="Grand Metropolitan Stadium",
160+
city="London",
161+
country="United Kingdom",
162+
stands=stands,
163+
)
164+
165+
166+
@strawberry.type
167+
class Query:
168+
@strawberry.field
169+
def stadium(self, seats_per_row: int) -> Stadium:
170+
return create_stadium(seats_per_row)
171+
172+
173+
ROOT = Path(__file__).parent / "queries"
174+
stadium_query = (ROOT / "stadium.graphql").read_text()
175+
176+
177+
@pytest.mark.benchmark
178+
@pytest.mark.parametrize(
179+
"seats_per_row", [250, 500], ids=lambda x: f"seats_per_row_{x}"
180+
)
181+
def test_stadium(benchmark: BenchmarkFixture, seats_per_row: int):
182+
"""Benchmark a complex nested query with a large dataset.
183+
184+
This test benchmarks the execution of a GraphQL query that returns
185+
a stadium with multiple stands, each containing thousands of seats.
186+
Each seat has multiple labels and coordinates.
187+
188+
The benchmark tests with different seat counts:
189+
- 250 seats/row: ~45,000 total seats
190+
- 500 seats/row: ~90,000 total seats
191+
"""
192+
schema = strawberry.Schema(query=Query)
193+
194+
def run():
195+
return asyncio.run(
196+
schema.execute(
197+
stadium_query, variable_values={"seatsPerRow": seats_per_row}
198+
)
199+
)
200+
201+
results = benchmark(run)
202+
203+
assert results.errors is None
204+
assert results.data is not None
205+
assert results.data["stadium"]["name"] == "Grand Metropolitan Stadium"

0 commit comments

Comments
 (0)