Request Flow Architecture¶
Last Updated: 2025-12-30 Status: Complete Audience: Developers wanting to understand FraiseQL's execution pipeline
Overview¶
FraiseQL processes GraphQL requests through an optimized pipeline that leverages PostgreSQL's native JSONB format and a Rust-based transformation layer for maximum performance.
High-Level Request Flow¶
flowchart LR
A[Client] -->|GraphQL Query| B[FastAPI/HTTP]
B -->|Parse| C[GraphQL Parser]
C -->|Validate| D[FraiseQL Schema]
D -->|Execute| E{Query Type?}
E -->|Query| F[Rust Pipeline]
E -->|Mutation| G[SQL Function]
F -->|SELECT| H[(PostgreSQL)]
G -->|CALL| H
H -->|JSONB| I[Rust Transform]
I -->|Field Selection| J[JSON Response]
J -->|HTTP| A
Key Insight: PostgreSQL returns data already in JSONB format. The Rust pipeline performs field selection and transformation without Python serialization overhead.
Detailed Request Pipeline¶
Step 1: HTTP Request → FastAPI¶
flowchart TD
A[HTTP POST /graphql] --> B{Request Type}
B -->|Standard Query| C[GraphQL Endpoint]
B -->|APQ Query| D[APQ Handler]
D -->|Cache Hit| E[Retrieve Query]
D -->|Cache Miss| F[Store & Parse]
C --> G[GraphQL Parser]
E --> G
F --> G
What happens: - FastAPI receives the HTTP request - APQ (Automatic Persisted Queries) checks cache for known queries - Standard queries are parsed directly - All paths lead to GraphQL parsing
Files involved:
- src/fraiseql/fastapi/routes.py - HTTP endpoint
- src/fraiseql/middleware/apq.py - APQ handling
Step 2: GraphQL Parsing & Validation¶
flowchart TD
A[GraphQL Query String] --> B[Parse AST]
B --> C{Valid Syntax?}
C -->|No| D[Return Syntax Error]
C -->|Yes| E[Schema Validation]
E --> F{Valid Against Schema?}
F -->|No| G[Return Validation Error]
F -->|Yes| H[Extract Operation]
H --> I[Query Complexity Analysis]
I --> J{Within Limits?}
J -->|No| K[Return Complexity Error]
J -->|Yes| L[Ready for Execution]
What happens: - GraphQL query is parsed into an Abstract Syntax Tree (AST) - Validated against the generated schema - Complexity analysis prevents DoS attacks - Operation details extracted for execution
Files involved:
- graphql-core library - Parsing and validation
- src/fraiseql/gql/complexity.py - Complexity analysis
- src/fraiseql/gql/schema_builder.py - Schema definition
Step 3: Query Execution (Read Path)¶
flowchart TD
A[GraphQL Query] --> B[Resolve Root Field]
B --> C{Has sql_source?}
C -->|Yes| D[Auto-Generated Resolver]
C -->|No| E[Custom Resolver Function]
D --> F[Build SQL SELECT]
E --> F
F --> G[Execute on PostgreSQL]
G --> H[PostgreSQL View]
H --> I[Returns JSONB Rows]
I --> J{Use Rust Pipeline?}
J -->|Yes| K[Rust Field Selection]
J -->|No| L[Python Serialization]
K --> M[Optimized JSON]
L --> M
The Rust Advantage:
| Approach | Steps | Performance |
|---|---|---|
| Traditional | PostgreSQL → Rows → ORM → Python Objects → GraphQL Serialize → JSON | ~10-50ms overhead |
| FraiseQL | PostgreSQL → JSONB → Rust Transform → JSON | ~0.1-1ms overhead |
What happens: - Root field resolver is called - SQL query is generated from GraphQL selection - PostgreSQL view returns data in JSONB format - Rust pipeline selects requested fields and transforms to JSON - Zero Python serialization for optimal performance
Files involved:
- src/fraiseql/core/rust_pipeline.py - Rust integration
- fraiseql_rs/src/lib.rs - Rust transformation code
- src/fraiseql/db.py - Database query execution
- src/fraiseql/sql/where_generator.py - SQL WHERE clause generation
Step 4: Mutation Execution (Write Path)¶
flowchart TD
A[GraphQL Mutation] --> B[Resolve Mutation Field]
B --> C[Extract Input Data]
C --> D[Validate Input Types]
D --> E{Validation OK?}
E -->|No| F[Return Validation Error]
E -->|Yes| G[Call SQL Function]
G --> H[fn_operation_name]
H --> I[(PostgreSQL)]
I --> J{Success?}
J -->|Yes| K[Return Success Type]
J -->|No| L[Return Error Type]
K --> M[Cascade Detection]
L --> M
M --> N{Related Data Changed?}
N -->|Yes| O[Invalidate Caches]
N -->|No| P[Return Response]
O --> P
CQRS Pattern: - Queries read from optimized views - Mutations call PostgreSQL functions - Clear separation of read/write concerns
What happens: - Mutation input is validated against input types - PostgreSQL function is called (CQRS pattern) - Function returns success/error as JSONB - Cascade detection checks for related data changes - Related caches are invalidated automatically
Files involved:
- src/fraiseql/mutations/mutation_decorator.py - Mutation decorator
- src/fraiseql/mutations/sql_generator.py - SQL function calls
- src/fraiseql/mutations/cascade_detection.py - Cache invalidation
- Database functions - fn_create_*, fn_update_*, etc.
Step 5: Response Formation¶
flowchart TD
A[Execution Result] --> B{Errors Present?}
B -->|Yes| C[Format Error Response]
B -->|No| D[Format Data Response]
C --> E[GraphQL Error Format]
D --> F[Apply Field Selection]
F --> G[Nested Type Resolution]
G --> H{Has Nested Types?}
H -->|Yes| I[Resolve Nested Fields]
H -->|No| J[Final JSON]
I --> J
E --> J
J --> K[Add Extensions]
K --> L[HTTP Response]
GraphQL Response Format:
{
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "john@example.com"
}
},
"errors": null,
"extensions": {
"complexity": 15,
"queryHash": "abc123..."
}
}
What happens: - Execution results are formatted as GraphQL response - Field selection is applied (only requested fields) - Nested types are resolved recursively - Extensions added (complexity score, tracing, etc.) - Final JSON sent over HTTP
Performance Characteristics¶
Traditional GraphQL Framework¶
flowchart LR
A[PostgreSQL] -->|Rows| B[ORM]
B -->|Objects| C[Python Memory]
C -->|Serialize| D[GraphQL]
D -->|JSON| E[Response]
style B fill:#ffcccc
style C fill:#ffcccc
style D fill:#ffcccc
Bottlenecks: - ❌ ORM deserialization (10-20ms per query) - ❌ Python object creation (5-10ms) - ❌ GraphQL serialization (10-30ms)
Total overhead: ~25-60ms per request
FraiseQL Pipeline¶
flowchart LR
A[PostgreSQL] -->|JSONB| B[Rust Pipeline]
B -->|Field Selection| C[JSON Response]
style B fill:#ccffcc
Advantages: - ✅ JSONB from PostgreSQL (native format) - ✅ Rust field selection (compiled performance) - ✅ Zero Python serialization
Total overhead: ~0.1-1ms per request
Speedup: 25-60x faster than traditional approaches
Monitoring Points¶
Key metrics to monitor in production:
flowchart TD
A[Request] --> B[🔍 HTTP Latency]
B --> C[🔍 GraphQL Parse Time]
C --> D[🔍 Schema Validation Time]
D --> E[🔍 Complexity Score]
E --> F{Query or Mutation?}
F -->|Query| G[🔍 SQL Execution Time]
F -->|Mutation| H[🔍 Function Execution Time]
G --> I[🔍 Rust Transform Time]
H --> I
I --> J[🔍 Total Response Time]
Monitoring files:
- src/fraiseql/monitoring/metrics/ - Prometheus metrics
- src/fraiseql/tracing/opentelemetry.py - OpenTelemetry integration
- src/fraiseql/analysis/query_analyzer.py - Query analysis
Caching Layers¶
FraiseQL implements multi-level caching:
flowchart TD
A[Request] --> B{APQ Cache Hit?}
B -->|Yes| C[Skip Query String Parse]
B -->|No| D[Parse Query]
C --> E{Schema Cache Hit?}
D --> E
E -->|Yes| F[Reuse Type Info]
E -->|No| G[Build Type Info]
F --> H{Result Cache Hit?}
G --> H
H -->|Yes| I[Return Cached Data]
H -->|No| J[Execute Query]
I --> K[Response]
J --> K
Cache Levels:
- APQ Cache - Query string hashing
- Schema Cache - Type and field metadata
- Result Cache - Query result caching (optional)
- DataLoader Cache - Batch/cache repeated queries
Files involved:
- src/fraiseql/middleware/apq.py - APQ caching
- src/fraiseql/cache/view_metadata.py - Schema caching
- src/fraiseql/optimization/decorators.py - DataLoader
Error Handling Flow¶
flowchart TD
A[Error Occurs] --> B{Error Type?}
B -->|Syntax Error| C[GraphQL Parser]
B -->|Validation Error| D[Schema Validator]
B -->|Execution Error| E[Resolver]
B -->|Database Error| F[PostgreSQL]
C --> G[Format GraphQL Error]
D --> G
E --> H{User-Defined Error?}
F --> I[Database Exception]
H -->|Yes| J[Custom Error Type]
H -->|No| K[Generic Error]
I --> K
J --> G
K --> G
G --> L[Error Response]
Error Response Format:
{
"data": null,
"errors": [
{
"message": "Field 'email' is required",
"locations": [{"line": 2, "column": 3}],
"path": ["createUser"],
"extensions": {
"code": "VALIDATION_ERROR",
"field": "email"
}
}
]
}
Connection Pooling¶
PostgreSQL connections are managed efficiently:
flowchart LR
A[Request 1] --> B[Connection Pool]
C[Request 2] --> B
D[Request 3] --> B
B --> E[Conn 1]
B --> F[Conn 2]
B --> G[Conn 3]
E --> H[(PostgreSQL)]
F --> H
G --> H
H --> I[Response]
I --> B
Pool Configuration: - Min connections: 2 - Max connections: 10 (configurable) - Timeout: 30 seconds - Recycle: 3600 seconds (1 hour)
Files involved:
- src/fraiseql/db.py - Connection pool setup
- src/fraiseql/fastapi/config.py - Pool configuration
Related Documentation¶
- Type System Architecture - How Python types map to GraphQL
- Trinity Pattern - Database identifier pattern
- CQRS Design - Query/Mutation separation
- Performance Guide - Optimization techniques
Summary¶
FraiseQL's request flow is optimized for:
✅ Performance - Rust pipeline eliminates Python serialization ✅ Scalability - Connection pooling and multi-level caching ✅ Security - Complexity analysis and validation ✅ Observability - Comprehensive monitoring and tracing ✅ CQRS - Clear separation of reads and writes
The exclusive Rust pipeline is the key differentiator, delivering 25-60x performance improvement over traditional GraphQL frameworks.