grpc_graphql_gateway 1.2.4

A Rust implementation of gRPC-GraphQL gateway - generates GraphQL execution code from gRPC services
# Query Cost Analysis

This guide explains how to use the Query Cost Analyzer to track, analyze, and enforce cost budgets for GraphQL queries, preventing expensive queries from spiking infrastructure costs.

## Overview

The Query Cost Analyzer assigns a "cost" to each GraphQL query based on its complexity and enforces budgets at both the query level and per-user level. This prevents expensive queries from overwhelming your infrastructure and helps maintain predictable costs.

## Key Benefits

- **Prevent Cost Spikes**: Block queries that exceed cost thresholds
- **User Budget Enforcement**: Limit query costs per user over time windows
- **Adaptive Cost Multipliers**: Automatically increase costs during high system load
- **Cost Analytics**: Track query costs and identify expensive patterns
- **Database Protection**: Prevent over-provisioning by blocking runaway queries

## Configuration

```rust
use grpc_graphql_gateway::{Gateway, QueryCostConfig};
use std::time::Duration;
use std::collections::HashMap;

let mut field_multipliers = HashMap::new();
field_multipliers.insert("user.posts".to_string(), 50); // 50x cost multiplier
field_multipliers.insert("posts.comments".to_string(), 100); // 100x cost multiplier

let cost_config = QueryCostConfig {
    max_cost_per_query: 1000,        // Reject queries above this cost
    base_cost_per_field: 1,          // Base cost per field
    field_cost_multipliers: field_multipliers,
    user_cost_budget: 10_000,        // Max cost per user per window
    budget_window: Duration::from_secs(60), // 1 minute window
    track_expensive_queries: true,   // Log costly queries
    expensive_percentile: 0.95,      // 95th percentile = "expensive"
    adaptive_costs: true,            // Increase costs during high load
    high_load_multiplier: 2.0,       // 2x cost during peak load
};
```

## Basic Usage

### Calculate Query Cost

```rust
use grpc_graphql_gateway::QueryCostAnalyzer;

let analyzer = QueryCostAnalyzer::new(cost_config);

let query = r#"
    query {
        user(id: 1) {
            id
            name
            posts {
                id
                title
                comments {
                    id
                    text
                }
            }
        }
    }
"#;

// Calculate cost
match analyzer.calculate_query_cost(query).await {
    Ok(result) => {
        println!("Query cost: {}", result.total_cost);
        println!("Field count: {}", result.field_count);
        println!("Complexity: {}", result.complexity);
    }
    Err(e) => {
        println!("Query rejected: {}", e);
        // Return error to client
    }
}
```

### Enforce User Budgets

```rust
let user_id = "user_123";
let query_cost = 250;

// Check if user has budget remaining
match analyzer.check_user_budget(user_id, query_cost).await {
    Ok(()) => {
        // User has budget, execute query
    }
    Err(e) => {
        // User exceeded budget
        println!("Budget exceeded: {}", e);
        // Return rate limit error to client
    }
}
```

## Integration with Gateway

Integrate the cost analyzer into your gateway middleware:

```rust
use grpc_graphql_gateway::{
    Gateway, QueryCostAnalyzer, QueryCostConfig, Middleware,
};
use axum::{
    extract::Extension,
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use std::sync::Arc;

// Create cost analyzer
let cost_analyzer = Arc::new(QueryCostAnalyzer::new(QueryCostConfig::default()));

// Middleware to check query costs
async fn cost_check_middleware(
    Extension(analyzer): Extension<Arc<QueryCostAnalyzer>>,
    query: String,
    user_id: String,
) -> Result<(), Response> {
    // Calculate query cost
    let cost_result = match analyzer.calculate_query_cost(&query).await {
        Ok(result) => result,
        Err(e) => {
            return Err((
                StatusCode::BAD_REQUEST,
                Json(serde_json::json!({
                    "error": "Query too complex",
                    "message": e,
                })),
            ).into_response());
        }
    };

    // Check user budget
    if let Err(e) = analyzer.check_user_budget(&user_id, cost_result.total_cost).await {
        return Err((
            StatusCode::TOO_MANY_REQUESTS,
            Json(serde_json::json!({
                "error": "Budget exceeded",
                "message": e,
            })),
        ).into_response());
    }

    Ok(())
}
```

## Field Cost Multipliers

Assign higher costs to expensive fields:

```rust
let mut multipliers = HashMap::new();

// Relationship fields (can cause N+1 queries)
multipliers.insert("user.posts".to_string(), 50);
multipliers.insert("user.followers".to_string(), 100);
multipliers.insert("post.comments".to_string(), 50);

// Aggregation fields (expensive computations)
multipliers.insert("analytics".to_string(), 200);
multipliers.insert("statistics".to_string(), 150);

// External API calls
multipliers.insert("thirdPartyData".to_string(), 500);

let config = QueryCostConfig {
    field_cost_multipliers: multipliers,
    ..Default::default()
};
```

## Adaptive Cost Multipliers

Automatically increase costs during high system load:

```rust
// Update load factor based on system metrics
let cpu_usage = 0.85; // 85% CPU
let memory_usage = 0.75; // 75% memory

analyzer.update_load_factor(cpu_usage, memory_usage).await;

// Costs will be automatically multiplied by high_load_multiplier
// when average load > 80%
```

## Cost Analytics

Track and analyze query costs:

```rust
// Get analytics
let analytics = analyzer.get_analytics().await;

println!("Total queries tracked: {}", analytics.total_queries);
println!("Average cost: {}", analytics.average_cost);
println!("Median cost: {}", analytics.median_cost);
println!("P95 cost: {}", analytics.p95_cost);
println!("P99 cost: {}", analytics.p99_cost);
println!("Max cost: {}", analytics.max_cost);

// Get threshold for "expensive" queries
let expensive_threshold = analyzer.get_expensive_threshold().await;
println!("Queries above {} are considered expensive", expensive_threshold);
```

## Periodic Cleanup

Clean up expired user budgets to prevent memory growth:

```rust
use tokio::time::{interval, Duration};

// Run cleanup every 5 minutes
let mut cleanup_interval = interval(Duration::from_secs(300));

tokio::spawn({
    let analyzer = Arc::clone(&cost_analyzer);
    async move {
        loop {
            cleanup_interval.tick().await;
            analyzer.cleanup_expired_budgets().await;
        }
    }
});
```

## Cost Optimization Strategies

### 1. Set Appropriate Base Costs

```rust
QueryCostConfig {
    base_cost_per_field: 1,  // Start with 1, adjust based on your schema
    max_cost_per_query: 1000, // Tune based on 95th percentile
    ..Default::default()
}
```

### 2. Identify Expensive Fields

```rust
// Get analytics to find expensive query patterns
let analytics = analyzer.get_analytics().await;

// Queries above P95 should be investigated
if query_cost > analytics.p95_cost {
    // Log for review
    println!("Expensive query detected: cost={}, query={}", query_cost, query);
}
```

### 3. Use Query Whitelisting

Combine with query whitelisting for production:

```rust
// Pre-calculate costs for whitelisted queries
// Reject ad-hoc expensive queries
Gateway::builder()
    .with_query_cost_config(cost_config)
    .with_query_whitelist(whitelist_config)
    .build()?
```

## Cost Impact

By implementing query cost analysis, you can:

| Benefit | Impact |
|---------|--------|
| **Prevent Runaway Queries** | Avoid database overload |
| **Predictable Costs** | No surprise cost spikes |
| **Fair Resource Allocation** | Per-user budgets prevent abuse |
| **Right-Size Infrastructure** | Avoid over-provisioning databases |

**Estimated Monthly Savings**: $200-500 by preventing over-provisioning and database spikes

## Example: E-commerce Schema

```rust
let mut multipliers = HashMap::new();

// Products (relatively cheap)
multipliers.insert("products".to_string(), 1);
multipliers.insert("product.reviews".to_string(), 20);

// Users (moderate cost)
multipliers.insert("user.orders".to_string(), 50);
multipliers.insert("user.wishlist".to_string(), 10);

// Analytics (expensive)
multipliers.insert("salesAnalytics".to_string(), 500);
multipliers.insert("trendingProducts".to_string(), 200);

let config = QueryCostConfig {
    base_cost_per_field: 1,
    max_cost_per_query: 2000,
    field_cost_multipliers: multipliers,
    user_cost_budget: 20_000,
    budget_window: Duration::from_secs(60),
    ..Default::default()
};
```

## Monitoring

Export cost metrics to Prometheus:

```rust
// Add to your metrics collection
gauge!("graphql_query_cost_p95", analytics.p95_cost as f64);
gauge!("graphql_query_cost_p99", analytics.p99_cost as f64);
counter!("graphql_queries_rejected_cost_limit", 1);
counter!("graphql_users_rate_limited_budget", 1);
```

## Best Practices

1. **Start Conservative**: Begin with high limits, then tune down based on analytics
2. **Monitor P95/P99**: Use percentiles to set thresholds, not max values
3. **Whitelist Production Queries**: Pre-approve and optimize expensive queries
4. **Test Under Load**: Verify adaptive cost multipliers work as expected
5. **Budget Windows**: Use 1-minute windows for APIs, 5-minute+ for dashboards

## Related Documentation

- [Cost Optimization Strategies]../production/cost-optimization-strategies.md
- [Query Whitelisting]../security/query-whitelisting.md
- [Performance Monitoring]../advanced/metrics.md