Skip to content

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:

  1. APQ Cache - Query string hashing
  2. Schema Cache - Type and field metadata
  3. Result Cache - Query result caching (optional)
  4. 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



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.