Find and fix slow GraphQL queries in your NetBox instance before they cause performance problems.
This tool analyzes your GraphQL queries and tells you:
- π¨ Which queries will be expensive to run
β οΈ Common anti-patterns that cause slowdowns- π‘ Specific recommendations to make queries faster
No NetBox changes required - this is a standalone analysis tool.
git clone https://github.com/netboxlabs/netbox-graphql-query-optimizer.git
cd netbox-graphql-query-optimizer
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install -e .
For security, use the NETBOX_TOKEN
environment variable:
export NETBOX_TOKEN=your_api_token_here
Run calibration to get accurate complexity scores based on your actual data:
netbox-gqo calibrate --url https://your-netbox.com/
This reads actual counts from your NetBox (devices, interfaces, IPs, etc.) and caches them for accurate analysis.
Save your GraphQL query to a file (e.g., my-query.graphql
):
query GetDevices {
device_list {
id
name
interfaces {
name
ip_addresses {
address
}
}
}
}
Run the analyzer:
netbox-gqo analyze my-query.graphql --url https://your-netbox.com/
You'll get instant feedback:
Query Analysis Summary
Depth 3
Complexity 20500 β οΈ HIGH!
Est. Rows 10000
Recommendations:
β pagination: List field 'device_list' has no pagination args
β pagination: List field 'interfaces' has no pagination args
β pagination: List field 'ip_addresses' has no pagination args
β fanout: 3 listβlist nests without pagination
The complexity score estimates how expensive your query is to run:
Score | Severity | What It Means |
---|---|---|
< 50 | β Good | Fast query, safe for production |
50-200 | May be slow, consider optimizing | |
200-500 | π₯ High | Will be slow, should optimize |
> 500 | π¨ Critical | Very expensive, must optimize |
How it's calculated:
Complexity = Ξ£ (Type Weight Γ Estimated Rows)
-
Type Weight: How expensive each object type is to fetch
- Device: 3 (heaviest - lots of relationships)
- Interface: 2 (medium)
- IPAddress: 1 (lightweight)
- VirtualMachine: 3
- Cable: 2
- Circuit: 2
- Rack: 2
- Site: 2
- VLAN: 1
- Other types: 1 (default)
-
Estimated Rows: Number of items the query will fetch
- If you use
limit
: that number - Otherwise: actual count from your NetBox (if calibrated) or 100 (default)
- If you use
Example:
device_list(pagination: {limit: 10}) {
interfaces(pagination: {limit: 5}) {
ip_addresses(pagination: {limit: 2})
}
}
Score = (10 devices Γ 3) + (50 interfaces Γ 2) + (100 IPs Γ 1) = 230
β pagination: List field 'device_list' has no pagination args
Problem: Without pagination, the query fetches ALL items (could be thousands).
Fix: Add pagination:
# β Bad - fetches all devices
device_list {
id
name
}
# β
Good - fetches only 20 devices
device_list(pagination: {offset: 0, limit: 20}) {
id
name
}
β fanout: 2 listβlist nests without pagination
Problem: Nested lists multiply! 100 devices Γ 10 interfaces = 1,000 database queries.
Fix: Add pagination at each level:
# β Bad - 100 Γ 50 Γ 10 = 50,000 items!
device_list {
interfaces {
ip_addresses {
address
}
}
}
# β
Good - 20 Γ 5 Γ 2 = 200 items
device_list(pagination: {limit: 20}) {
interfaces(pagination: {limit: 5}) {
ip_addresses(pagination: {limit: 2}) {
address
}
}
}
β depth: Depth 7 > 5
Problem: Deeply nested queries are hard to optimize and can cause timeouts.
Fix: Simplify your query or make separate queries.
β’ filter-pushdown: Consider applying filters at 'device_list' using: filters
Recommendation: Use filters to reduce the number of items:
# β
Better - filter before fetching
device_list(
pagination: {limit: 50}
filters: {site: "DC1", status: "active"}
) {
id
name
}
β overfetch: Query requests 87 total fields (consider requesting only necessary fields)
Problem: Requesting too many fields increases payload size, database load, and response time. Every field requires database access and serialization.
Fix: Only request fields you actually need:
# β Bad - requesting 87 fields including many unused ones
device_list(pagination: {limit: 100}) {
id
name
serial
status
asset_tag
comments
created
last_updated
# ... 79 more fields including deep nesting
interfaces {
# many fields...
ip_addresses {
# many fields...
}
}
}
# β
Good - only request what you need (15 fields)
device_list(pagination: {limit: 100}) {
id
name
serial
status
site {
name
}
primary_ip4 {
address
}
}
Thresholds:
-
50 fields: INFO warning (consider trimming)
-
75 fields: WARN warning (definitely trim unused fields)
- Per-object > 15 fields: Breadth warning (may be requesting too much from one object)
query BadQuery {
device_list {
name
interfaces {
name
ip_addresses {
address
}
}
}
}
Analysis:
Complexity: 20,500 π¨ CRITICAL
Est. Rows: 10,000
Warnings: 3 pagination issues, fan-out detected
query GoodQuery {
device_list(
pagination: {limit: 20}
filters: {status: "active"}
) {
name
interfaces(pagination: {limit: 5}) {
name
ip_addresses(pagination: {limit: 2}) {
address
}
}
}
}
Analysis:
Complexity: 17 β
GOOD
Est. Rows: 20
Warnings: None
Result: 1,200Γ faster! (20,500 β 17)
Calibration is essential for getting accurate complexity scores. Without calibration, the tool uses default estimates (100 items per list). With calibration, it uses your actual NetBox data counts.
How it works:
- Probes your NetBox REST API - Reads actual counts for each object type (devices, interfaces, IPs, etc.)
- Caches the results - Stores counts in
~/.netbox-gqo/calibration/<host>.json
- Uses real data in analysis - When analyzing queries, multiplies type weights by your actual counts instead of defaults
Example impact:
Without Calibration | With Calibration (Your Data) |
---|---|
Assumes 100 devices | Uses your actual count (e.g., 2,500 devices) |
Assumes 100 interfaces per device | Uses your actual count (e.g., 15 interfaces avg) |
Score: 300 | Score: 7,800 (reflects reality) |
Running calibration:
# Set your API token (recommended - keeps token out of shell history)
export NETBOX_TOKEN=your_api_token_here
# Basic calibration
netbox-gqo calibrate --url https://your-netbox.com/
# Query-specific calibration (only probes types used in your query)
netbox-gqo calibrate --url https://your-netbox.com/ --query my-query.graphql
# Alternative: pass token directly with --token flag (less secure)
netbox-gqo calibrate --url https://your-netbox.com/ --token YOUR_API_TOKEN
What gets calibrated:
The tool probes these NetBox REST endpoints:
/api/dcim/devices/
β Device count/api/dcim/interfaces/
β Interface count/api/ipam/ip-addresses/
β IP address count/api/virtualization/virtual-machines/
β VM count/api/dcim/cables/
β Cable count/api/circuits/circuits/
β Circuit count/api/dcim/racks/
β Rack count/api/dcim/sites/
β Site count/api/ipam/vlans/
β VLAN count
You can customize the type mappings in your config file to add more types or adjust endpoints.
When to recalibrate:
- After significant data changes (added/removed many devices)
- Periodically (monthly/quarterly) for production systems
- Before analyzing queries for capacity planning
Fail builds if queries are too complex:
# In your CI pipeline
# Set NETBOX_TOKEN as a secret environment variable in your CI system
export NETBOX_TOKEN=$NETBOX_SECRET
netbox-gqo analyze query.graphql \
--url https://netbox.com/ \
--fail-on-score 200 \
--output json
Exit codes:
0
= Query is okay2
= Query exceeds complexity threshold
For automation and tooling:
netbox-gqo analyze query.graphql --url https://netbox.com/ --output json
{
"score": 410,
"depth": 3,
"fanout": 2,
"rows": 400,
"findings": [
{
"rule_id": "pagination",
"message": "List field 'device_list' has no pagination args",
"severity": "WARN"
}
]
}
Create ~/.netbox-gqo/config.yaml
for custom settings:
# Your NetBox URL
default_url: https://netbox.example.com/
# Thresholds (defaults shown)
max_depth: 5 # Warn if query nests deeper than this
max_aliases: 10 # Warn if more than this many aliases
breadth_warn: 15 # Warn if single object requests > 15 fields
leaf_warn: 20 # Warn if single object has > 20 scalar fields
pagination_default: 100 # Default row estimate if not calibrated
# Type weights (how expensive each object type is to fetch)
type_weights:
Device: 3 # Devices are expensive (lots of relationships)
Interface: 2 # Medium cost
IPAddress: 1 # Lightweight
VirtualMachine: 3
Cable: 2
Circuit: 2
Rack: 2
Site: 2
VLAN: 1
# Type mappings for calibration (GraphQL type β REST endpoint)
type_mappings:
Device: dcim/devices
Interface: dcim/interfaces
IPAddress: ipam/ip-addresses
VirtualMachine: virtualization/virtual-machines
Cable: dcim/cables
Circuit: circuits/circuits
Rack: dcim/racks
Site: dcim/sites
VLAN: ipam/vlans
- Always use pagination on list fields
- Start with small limits (10-20) while developing
- Use filters to reduce data at the source
- Keep queries shallow (depth β€ 3 is ideal)
- Run the analyzer before deploying new queries
- Calibrate for production systems to get accurate scores
- Don't fetch all items without pagination
- Don't nest lists without limits at each level
- Don't request fields you don't need
- Don't ignore high complexity scores
- Don't skip calibration for production systems
Problem: You haven't specified your NetBox URL.
Fix:
# Option 1: Pass URL on command line
netbox-gqo analyze query.graphql --url https://your-netbox.com/
# Option 2: Set in config file
echo "default_url: https://your-netbox.com/" > ~/.netbox-gqo/config.yaml
Problem: Can't reach your NetBox GraphQL endpoint.
Fix:
# If authentication is required, set your token
export NETBOX_TOKEN=your_api_token_here
netbox-gqo schema pull --url https://your-netbox.com/
# Verify the GraphQL endpoint is accessible
curl https://your-netbox.com/graphql/
Problem: Your query has a complexity score > 200.
Fix:
- Add pagination to all list fields
- Reduce
limit
values - Add filters to reduce result set
- Split into multiple smaller queries
- Schema Loading: Fetches your NetBox GraphQL schema (cached for speed)
- Query Parsing: Validates your query against the schema
- AST Analysis: Walks the query structure to find patterns
- Rule Checking: Applies best-practice rules (8 rules total)
- Cost Calculation: Estimates complexity based on types and counts
- Reporting: Shows findings and recommendations
All analysis is static - no queries are actually run against your NetBox.
Q: Will this slow down my NetBox? A: No! The tool only reads your schema (once, then cached). It never runs your actual queries.
Q: Do I need to modify NetBox? A: No. This is a standalone CLI tool.
Q: What version of NetBox is supported? A: Any version with GraphQL support (NetBox 3.3+).
Q: How accurate are the complexity scores? A: Without calibration: Reasonable estimates. With calibration: Very accurate for your specific deployment.
Q: Can I use this with other GraphQL APIs? A: It's optimized for NetBox, but the core analysis works with any GraphQL API. You may need to adjust type weights and mappings.
Q: What if I don't have an API token?
A: For public/unauthenticated NetBox instances, you don't need to set NETBOX_TOKEN
. For authenticated instances, you'll need a token (set via NETBOX_TOKEN
environment variable or --token
flag) for both schema pulling and calibration.
Q: Is it safe to use the --token flag?
A: Using --token
on the command line is less secure because it appears in shell history and process lists. We recommend using the NETBOX_TOKEN
environment variable instead, which keeps your token out of logs and history files.
- Issues: https://github.com/netboxlabs/netbox-graphql-query-optimizer/issues
- Discussions: https://github.com/netboxlabs/netbox-graphql-query-optimizer/discussions
Apache License 2.0 - see LICENSE file for details.