Skip to content

title: FraiseQL Advanced Optimization description: Advanced optimization techniques for FraiseQL deployments on PostgreSQL, beyond baseline performance characteristics. keywords: ["design", "scalability", "performance", "patterns", "security"] tags: ["documentation", "reference"]


FraiseQL Advanced Optimization

Status: Guide Last Updated: January 2026


Executive Summary

This guide covers advanced optimization techniques for FraiseQL deployments, beyond baseline performance characteristics. FraiseQL is a Python runtime GraphQL framework for PostgreSQL that builds its schema in memory at app startup and runs over FastAPI; queries resolve against your v_/tv_ PostgreSQL views and mutations call fn_ functions. Most optimization work therefore happens in PostgreSQL itself (indexes, views, partitioning) and in the surrounding infrastructure (caching, connection pooling, scaling). This guide addresses:

  • Query Optimization: PostgreSQL execution plans, predicate placement, index-aware filtering
  • Database Tuning: Index design, partitioning, materialized views, statistics
  • Caching Edge Cases: Hot keys, thundering herd, cache eviction policies
  • Multi-Instance Scaling: Consistency across replicas, session affinity, load balancing
  • Resource Optimization: Memory management, connection pooling, GC tuning
  • Monitoring & Profiling: Identifying bottlenecks, distributed tracing
  • Emergency Procedures: Circuit breakers, graceful degradation, backpressure

1. Query Optimization

FraiseQL generates parameterized SQL against your v_/tv_ PostgreSQL views at runtime and lets PostgreSQL's own query planner choose execution plans, join algorithms, and index usage. There is no separate FraiseQL query compiler or optimizer — the optimization surface is your view SQL, your indexes, and PostgreSQL's planner. Your job is to write selective views, index the right columns, and verify the plans PostgreSQL chooses with EXPLAIN.

1.1 Execution Plan Analysis

Inspect the plan PostgreSQL produces for the SQL behind a view, including actual timing and buffer hits, with EXPLAIN (ANALYZE, BUFFERS):

EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT id, data
FROM v_user
WHERE (data->>'tenant_id') = '...'
ORDER BY (data->>'created_at') DESC
LIMIT 50;

Read the plan top-down: look for sequential scans on large tables (missing or unused index), high actual rows versus estimated rows (stale statistics — run ANALYZE), and expensive sorts that could be served by an index. The Buffers line tells you how much I/O the query did.

1.2 Predicate Placement

PostgreSQL pushes WHERE predicates down to index/heap scans automatically, but only when the predicate is sargable (can use an index). Make sure the columns your GraphQL filters target are indexed and that the view does not wrap them in a way that defeats the index:

-- Indexed, sargable: planner can use idx on (data->>'tenant_id')
CREATE INDEX idx_user_tenant
ON tb_user ((data->>'tenant_id'))
WHERE deleted_at IS NULL;

-- For JSONB containment filters, a GIN index lets @> use the index
CREATE INDEX idx_user_data_gin ON tb_user USING GIN (data);

FraiseQL's WHERE operators (eq, gt, contains, @>, etc.) translate to parameterized SQL predicates; whether they use an index is entirely down to the indexes you create on the underlying table or expression.

1.3 Join Order

When a view joins several tables, PostgreSQL's planner reorders the joins for you based on table statistics — there is no FraiseQL-level join reordering. Keep the planner well-informed and the joins cheap:

  • Run ANALYZE so row-count estimates are accurate (see Section 2.4).
  • Index foreign-key columns so the planner can choose nested-loop + index lookups for selective joins instead of large hash joins.
  • Filter as early as possible inside the view (push selective WHERE clauses onto the driving table) so intermediate result sets stay small.
  • For very large joins, consider materializing the result in a tv_ projection table refreshed by a function or trigger, so GraphQL reads hit a single pre-composed table.

1.4 Adapting to Runtime Cardinality

PostgreSQL adapts execution to actual data at runtime (e.g. choosing nested-loop vs. hash join from statistics, and parallel workers from max_parallel_workers_per_gather). You influence this by keeping statistics fresh and, where the planner consistently misestimates, by adding extended statistics:

-- Help the planner when two columns are correlated
CREATE STATISTICS stat_user_tenant_status (dependencies)
ON tenant_id, status FROM tb_user;
ANALYZE tb_user;

2. Database Tuning

2.1 Index Design Strategy

class IndexDesignGuide:
    """Guidelines for optimal index design"""

    # Choose index type based on query pattern
    INDEX_SELECTION = {
        "high_selectivity_equality": "B-tree",      # WHERE id = 123
        "range_queries": "B-tree",                   # WHERE created_at > now() - interval
        "full_text_search": "GiST or GIN",         # WHERE content LIKE '%search%'
        "spatial_data": "GIST or BRIN",            # WHERE location <-> point < distance
        "json_contains": "GIN",                      # WHERE data @> '{"key": "value"}'
        "ordered_results": "B-tree DESC",           # ORDER BY created_at DESC
        "filtering_many_values": "Hash Index",      # WHERE status IN (...)
    }

    # Index design principles
    PRINCIPLES = {
        "selectivity": "Choose columns that filter most rows",
        "cardinality": "Prefer high-cardinality columns",
        "ordering": "Order matters for range queries",
        "covering": "Include all query columns to avoid table scan",
        "partial": "Exclude NULL or soft-deleted rows",
        "statistics": "ANALYZE regularly for query planning",
    }

    # Anti-patterns
    ANTI_PATTERNS = {
        "over_indexing": "Too many indexes = slow writes",
        "low_selectivity": "Index on status (only 3 values)",
        "unused_indexes": "Bloat without benefit",
        "foreign_key_missing": "No index on FK = slow joins",
        "wrong_direction": "Index on created_at ASC but query needs DESC",
    }


# Example: Comprehensive index strategy for users table
@fraiseql.type(sql_source="v_user", jsonb_column="data")
class User:
    id: ID
    email: str
    created_at: datetime
    updated_at: datetime
    tenant_id: ID
    status: str  # Only 3 values: active, inactive, suspended
    metadata: dict[str, Any]


# Define indexes
INDEX_STRATEGY = {
    # Primary key
    "pk_user": {
        "columns": ["id"],
        "type": "primary",
        "reason": "Primary key"
    },
    # Foreign key lookup
    "idx_user_tenant_id": {
        "columns": ["tenant_id"],
        "type": "b-tree",
        "where": "deleted_at IS NULL",
        "reason": "Fast tenant filtering"
    },
    # Email uniqueness and lookup
    "idx_user_email_unique": {
        "columns": ["email"],
        "type": "b-tree",
        "unique": True,
        "where": "deleted_at IS NULL",
        "reason": "Email uniqueness + fast login lookup"
    },
    # Time-range queries
    "idx_user_created_at": {
        "columns": ["created_at"],
        "type": "b-tree",
        "reason": "Range queries like 'users created in last 30 days'"
    },
    # Composite: tenant + status (common filter combination)
    "idx_user_tenant_status": {
        "columns": ["tenant_id", "status"],
        "type": "b-tree",
        "where": "deleted_at IS NULL",
        "reason": "Most common filter pattern"
    },
    # JSON search on metadata
    "idx_user_metadata_gin": {
        "columns": ["metadata"],
        "type": "gin",
        "reason": "Search within JSONB metadata"
    },
    # Covering index: includes all columns needed by common query
    "idx_user_tenant_email_covering": {
        "columns": ["tenant_id", "email"],
        "includes": ["id", "status"],  # PostgreSQL 11+ INCLUDE
        "type": "b-tree",
        "where": "deleted_at IS NULL",
        "reason": "Covering index for 'get active users by email' query"
    }
}

# SQL generation
CREATE_INDEXES_SQL = """
-- Primary key
CREATE INDEX idx_user_id ON tb_user (id);

-- Foreign key
CREATE INDEX idx_user_tenant_id ON tb_user (tenant_id)
WHERE deleted_at IS NULL;

-- Unique constraint
CREATE UNIQUE INDEX idx_user_email_unique ON tb_user (email)
WHERE deleted_at IS NULL;

-- Range queries
CREATE INDEX idx_user_created_at ON tb_user (created_at);

-- Composite: common filter combination
CREATE INDEX idx_user_tenant_status ON tb_user (tenant_id, status)
WHERE deleted_at IS NULL;

-- JSON search
CREATE INDEX idx_user_metadata_gin ON tb_user USING GIN (metadata);

-- Covering index for common query pattern
CREATE INDEX idx_user_tenant_email_covering ON tb_user (tenant_id, email)
INCLUDE (id, status)
WHERE deleted_at IS NULL;
"""

2.2 Materialized Views for Complex Queries

class MaterializedViewStrategy:
    """Use materialized views to pre-compute expensive aggregations"""

    # Example: Slow query that becomes fast with MV
    SLOW_QUERY = """
    SELECT
        user_id,
        COUNT(*) as total_posts,
        AVG(view_count) as avg_views,
        MAX(created_at) as latest_post_date
    FROM tb_post
    WHERE tenant_id = $1
    GROUP BY user_id
    """

    MATERIALIZED_VIEW = """
    CREATE MATERIALIZED VIEW mv_user_post_stats AS
    SELECT
        user_id,
        COUNT(*) as total_posts,
        AVG(view_count) as avg_views,
        MAX(created_at) as latest_post_date
    FROM tb_post
    WHERE deleted_at IS NULL
    GROUP BY user_id;

    CREATE INDEX idx_mv_user_post_stats_user_id
    ON mv_user_post_stats (user_id);
    """

    # Refresh strategy: incremental vs full
    REFRESH_STRATEGY = {
        "full_refresh": {
            "frequency": "daily",
            "cost": "high",
            "latency": "minutes",
            "use_for": "low-cardinality aggregations"
        },
        "incremental_refresh": {
            "frequency": "5 minutes",
            "cost": "low",
            "latency": "< 1 minute",
            "use_for": "frequently-changing data"
        },
        "event_driven_refresh": {
            "frequency": "on insert/update",
            "cost": "medium",
            "latency": "< 100ms",
            "use_for": "critical aggregations"
        }
    }

2.3 Partitioning Strategy

class PartitioningStrategy:
    """Partition large tables for performance and maintenance"""

    PARTITIONING_OPTIONS = {
        "range": {
            "use_for": "Time-series data",
            "example": "PARTITION BY RANGE (created_at)",
            "benefits": "Efficient time-range queries, easier archival",
            "cost": "Moderate"
        },
        "hash": {
            "use_for": "Even distribution across nodes",
            "example": "PARTITION BY HASH (user_id)",
            "benefits": "Load balancing, parallel queries",
            "cost": "Low"
        },
        "list": {
            "use_for": "Categorical data",
            "example": "PARTITION BY LIST (country)",
            "benefits": "Logical grouping, country-specific access",
            "cost": "Low"
        }
    }

    # Example: Time-series partitioning
    POSTS_PARTITIONING = """
    CREATE TABLE tb_post_base (
        id UUID,
        user_id UUID,
        content TEXT,
        created_at TIMESTAMP,
        deleted_at TIMESTAMP
    ) PARTITION BY RANGE (EXTRACT(YEAR FROM created_at));

    -- Quarterly partitions for current year
    CREATE TABLE tb_post_2025_q1 PARTITION OF tb_post_base
    FOR VALUES FROM (2025) TO (2026);

    CREATE TABLE tb_post_2025_q2 PARTITION OF tb_post_base
    FOR VALUES FROM (2026) TO (2027);

    -- Archive older data
    CREATE TABLE tb_post_2024 PARTITION OF tb_post_base
    FOR VALUES FROM (2024) TO (2025);

    -- Benefits:
    -- - Queries on 2025 data skip 2024 partitions
    -- - Can archive 2024 partition to slower storage
    -- - Faster VACUUM (partition-level)
    -- - Parallel sequential scans across partitions
    """

2.4 Query Statistics

class QueryStatisticsManager:
    """Maintain statistics for query optimization"""

    async def analyze_tables(self, tables: list[str]) -> None:
        """Update table statistics for query planner

        Without stats, planner guesses row counts (inefficient)
        With stats, planner makes optimal decisions
        """
        for table in tables:
            await self.db.execute(f"ANALYZE {table}")

    async def analyze_column(self, table: str, column: str) -> None:
        """Analyze specific column (e.g., after data update)"""
        await self.db.execute(f"ANALYZE {table} ({column})")

    async def view_column_statistics(
        self,
        table: str,
        column: str
    ) -> dict[str, Any]:
        """Inspect column statistics for debugging"""
        result = await self.db.query("""
            SELECT
                attname,
                n_distinct,
                n_distinct_inherited,
                avg_width,
                correlation
            FROM pg_stats
            WHERE tablename = $1 AND attname = $2
        """, table, column)

        return {
            "column": column,
            "n_distinct": result["n_distinct"],  # Cardinality
            "avg_width": result["avg_width"],    # Bytes per value
            "correlation": result["correlation"],  # Index effectiveness
        }

    # Update statistics regularly
    MAINTENANCE_SCHEDULE = {
        "analyze_all": "Daily during off-peak hours",
        "vacuum_full": "Weekly for heavily-updated tables",
        "reindex": "Monthly for index fragmentation",
    }

3. Caching Edge Cases

3.1 Hot Key Problem

When a single cache key receives massive traffic:

class HotKeyDetector:
    """Detect and handle hot keys (single keys with extreme traffic)"""

    def __init__(self, redis_client, threshold: int = 100):
        """Initialize detector

        Args:
            threshold: Operations/second for key to be considered "hot"
        """
        self.redis = redis_client
        self.threshold = threshold
        self.hot_keys = {}

    async def detect_hot_keys(self) -> list[str]:
        """Identify keys exceeding traffic threshold

        Hot key examples:
        - Popular user profile (celebrity)
        - High-traffic API endpoint config
        - System-wide counter (concurrent users)
        """
        # Use Redis keyspace notifications to track access patterns
        hot_keys = []

        for key_pattern in self._get_monitored_keys():
            ops_per_second = await self._estimate_ops_per_second(key_pattern)
            if ops_per_second > self.threshold:
                hot_keys.append((key_pattern, ops_per_second))

        return sorted(hot_keys, key=lambda x: x[1], reverse=True)

    async def _estimate_ops_per_second(self, key: str) -> float:
        """Estimate operations/second for key"""
        # Track operations in sliding window
        counter_key = f"ops_count:{key}"
        count = await self.redis.incr(counter_key)
        await self.redis.expire(counter_key, 1)  # Reset every second

        return float(count)

    async def mitigate_hot_key(self, key: str) -> None:
        """Apply hot key mitigation strategies"""

        strategy = self._select_strategy(key)

        if strategy == "local_cache":
            # Keep value in local memory cache
            await self._enable_local_caching(key)

        elif strategy == "read_through":
            # Serve stale value while refreshing in background
            await self._enable_stale_cache(key)

        elif strategy == "probabilistic":
            # Cache only for random subset of requests
            await self._enable_probabilistic_caching(key)

        elif strategy == "replication":
            # Replicate across multiple cache nodes
            await self._enable_cache_replication(key)


class LocalCacheForHotKeys:
    """Local in-memory cache for hot keys"""

    def __init__(self, max_keys: int = 100):
        self.cache = {}
        self.max_keys = max_keys

    async def get(self, key: str, redis_client) -> Any:
        """Try local cache first, fall back to Redis"""
        # Local cache: ~1 microsecond
        if key in self.cache:
            value, ttl = self.cache[key]
            if datetime.utcnow() < ttl:
                return value

        # Redis: ~5 milliseconds
        value = await redis_client.get(key)
        if value:
            # Store in local cache with TTL
            self.cache[key] = (value, datetime.utcnow() + timedelta(seconds=5))
            if len(self.cache) > self.max_keys:
                self._evict_lru()
        return value

    def _evict_lru(self) -> None:
        """Evict least recently used entry"""
        oldest_key = min(
            self.cache.keys(),
            key=lambda k: self.cache[k][1]
        )
        del self.cache[oldest_key]

3.2 Thundering Herd Problem

When cache expires and many requests try to refresh simultaneously:

class ThunderingHerdMitigation:
    """Prevent cache stampede when popular key expires"""

    async def get_with_mitigation(
        self,
        key: str,
        fetch_fn,
        cache_ttl: int = 300
    ) -> Any:
        """Get value with thundering herd prevention

        Standard cache miss: All 1000 requests compute value (1000x work!)

        With mitigation: First request computes, others wait
        """
        # Use Redis SET with NX (only if not exists)
        lock_key = f"lock:{key}"
        compute_sem = f"computing:{key}"

        # Try to acquire compute lock
        acquired = await self.redis.set(
            compute_sem,
            "true",
            nx=True,
            ex=1  # Lock expires in 1 second (recompute)
        )

        if acquired:
            # We won the race - compute value
            try:
                value = await fetch_fn()
                await self.redis.set(key, value, ex=cache_ttl)
                return value
            finally:
                await self.redis.delete(compute_sem)
        else:
            # Another request is computing - wait for result
            for attempt in range(100):  # Wait up to 5 seconds
                value = await self.redis.get(key)
                if value:
                    return value
                await asyncio.sleep(0.05)  # Poll every 50ms

            # Timeout - fall back to computing
            return await fetch_fn()


# Alternative: Probabilistic early refresh
class ProbabilisticEarlyRefresh:
    """Refresh cache before expiry for popular keys"""

    async def get_with_early_refresh(
        self,
        key: str,
        fetch_fn,
        cache_ttl: int = 300,
        early_refresh_probability: float = 0.1
    ) -> Any:
        """Cache with probabilistic refresh before expiry

        Idea: On access, randomly refresh cache early (10% of time)
        Result: Cache always fresh, no thundering herd on expiry
        """
        value = await self.redis.get(key)

        if value:
            # Cache hit - maybe refresh early?
            if random.random() < early_refresh_probability:
                # Refresh in background (don't block current request)
                asyncio.create_task(self._refresh_in_background(
                    key, fetch_fn, cache_ttl
                ))
            return value

        # Cache miss - compute and cache
        value = await fetch_fn()
        await self.redis.set(key, value, ex=cache_ttl)
        return value

    async def _refresh_in_background(self, key, fetch_fn, ttl):
        """Refresh cache value without blocking client"""
        try:
            value = await fetch_fn()
            await self.redis.set(key, value, ex=ttl)
        except Exception as e:
            logger.exception(f"Background refresh failed for {key}: {e}")

3.3 Cache Eviction Policies

class CacheEvictionPolicy(Enum):
    """Cache eviction strategies when full"""

    # LRU: Evict least recently used
    LRU = "lru"
    # Cost: Low CPU, good for working sets
    # Benefit: Recently accessed data stays

    # LFU: Evict least frequently used
    LFU = "lfu"
    # Cost: Medium CPU (track frequency)
    # Benefit: Popular data stays

    # FIFO: Evict oldest entry
    FIFO = "fifo"
    # Cost: Very low CPU
    # Benefit: Predictable order

    # Random: Evict random entry
    RANDOM = "random"
    # Cost: Minimal CPU
    # Benefit: Simple, works surprisingly well

    # TTL: Evict expired entries
    TTL = "ttl"
    # Cost: Medium (cleanup)
    # Benefit: Respects time boundaries


class EvictionPolicySelector:
    """Choose optimal eviction policy for workload"""

    @staticmethod
    def recommend_policy(workload_type: str) -> CacheEvictionPolicy:
        """Recommend policy based on workload"""

        if workload_type == "user_profiles":
            # Popular users accessed repeatedly
            return CacheEvictionPolicy.LFU

        elif workload_type == "session_cache":
            # Newer sessions matter more
            return CacheEvictionPolicy.FIFO

        elif workload_type == "api_responses":
            # Working set of recent responses
            return CacheEvictionPolicy.LRU

        elif workload_type == "feature_flags":
            # Rarely accessed, just don't expire
            return CacheEvictionPolicy.TTL

        else:
            # Default: Simple and effective
            return CacheEvictionPolicy.LRU

4. Multi-Instance Scaling

4.1 Consistency Across Replicas

class MultiInstanceConsistencyManager:
    """Ensure consistency when running multiple instances"""

    def __init__(self, primary_db: Database, replica_db: Database):
        self.primary = primary_db
        self.replica = replica_db

    # Challenge: Replica lag
    # ┌─────────────────┐
    # │ Primary writes  │ T0
    # │ X = 10          │
    # └─────────────────┘
    #         │
    #         │ Replication lag (5-100ms)
    #         ↓
    # ┌─────────────────┐
    # │ Replica reads   │ T0
    # │ X = ??? (old)   │
    # └─────────────────┘

    async def write_and_verify(
        self,
        query: str,
        params: list,
        verification_key: str,
        expected_value: Any
    ) -> None:
        """Write to primary, verify on replica"""

        # Write to primary
        result = await self.primary.execute(query, params)

        # Verify it reached replica
        verified = await self._wait_for_replica_consistency(
            verification_key,
            expected_value,
            timeout_ms=1000
        )

        if not verified:
            logger.warning(
                f"Replica lag detected: {verification_key} not yet updated"
            )

    async def _wait_for_replica_consistency(
        self,
        key: str,
        expected_value: Any,
        timeout_ms: int = 1000
    ) -> bool:
        """Poll replica until it catches up"""

        end_time = time.time() + (timeout_ms / 1000.0)

        while time.time() < end_time:
            replica_value = await self.replica.query_one(
                f"SELECT * FROM table WHERE id = $1",
                [key]
            )

            if replica_value == expected_value:
                return True

            await asyncio.sleep(0.01)  # Poll every 10ms

        return False


class ReadConsistencyLevel(Enum):
    """Choose read consistency vs performance trade-off"""

    # Read from primary (always consistent, slower)
    STRONG = "strong"

    # Read from replica after write (eventual, faster)
    # Use: If you can tolerate 100ms stale data
    EVENTUAL = "eventual"

    # Read from replica, fall back to primary on miss
    # Use: Hybrid approach, best for most cases
    HYBRID = "hybrid"

4.2 Session Affinity

class SessionAffinityManager:
    """Route requests to same instance for connection locality"""

    def __init__(self, instances: list[str]):
        self.instances = instances
        self.session_to_instance = {}

    def get_instance_for_session(self, session_id: str) -> str:
        """Get instance for session (consistent routing)

        Benefits:
        - Connection pooling is effective
        - Local caches stay warm
        - Database connections reused
        """
        if session_id not in self.session_to_instance:
            # Hash session to instance (consistent)
            instance_index = hash(session_id) % len(self.instances)
            self.session_to_instance[session_id] = self.instances[instance_index]

        return self.session_to_instance[session_id]

    def hash_consistent(self, key: str, replicas: int) -> int:
        """Consistent hashing for load balancing

        Without consistent hashing:
        - Add instance → 2/3 requests rehash (cache thrash)

        With consistent hashing:
        - Add instance → only 1/n requests rehash
        """
        # Ketama algorithm
        hash_value = self._compute_hash(key)
        return hash_value % replicas

4.3 Load Balancing Strategies

class LoadBalancingStrategy(Enum):
    """Different load balancing approaches"""

    # Round-robin: cycle through instances
    ROUND_ROBIN = "round_robin"

    # Least connections: send to instance with fewest active connections
    LEAST_CONNECTIONS = "least_connections"

    # Weighted round-robin: allocate based on capacity
    WEIGHTED = "weighted"

    # Consistent hash: same key always routes to same instance
    CONSISTENT_HASH = "consistent_hash"

    # IP hash: route based on client IP
    IP_HASH = "ip_hash"


class LoadBalancer:
    """Route requests across multiple instances"""

    def __init__(self, strategy: LoadBalancingStrategy):
        self.strategy = strategy
        self.instances = []
        self.round_robin_index = 0

    async def get_next_instance(
        self,
        request_context: dict[str, Any] | None = None
    ) -> str:
        """Get next instance for request"""

        if self.strategy == LoadBalancingStrategy.ROUND_ROBIN:
            instance = self.instances[self.round_robin_index]
            self.round_robin_index = (self.round_robin_index + 1) % len(self.instances)
            return instance

        elif self.strategy == LoadBalancingStrategy.LEAST_CONNECTIONS:
            return min(
                self.instances,
                key=lambda i: self._get_connection_count(i)
            )

        elif self.strategy == LoadBalancingStrategy.CONSISTENT_HASH:
            session_id = request_context.get("session_id", "")
            return self._consistent_hash(session_id)

        elif self.strategy == LoadBalancingStrategy.IP_HASH:
            client_ip = request_context.get("client_ip", "")
            return self._ip_hash(client_ip)

        else:
            return self.instances[0]  # Default

5. Resource Optimization

5.1 Memory Management

class MemoryOptimizer:
    """Optimize memory usage in FraiseQL runtime"""

    # Memory profiling
    @staticmethod
    async def profile_memory_usage() -> dict[str, int]:
        """Measure memory usage by component

        Returns:
            Dict mapping component -> bytes used
        """
        import psutil
        process = psutil.Process()
        memory_info = process.memory_info()

        return {
            "rss": memory_info.rss,        # Resident set size (physical RAM)
            "vms": memory_info.vms,        # Virtual memory size
            "percent": process.memory_percent(),
        }

    # Garbage collection tuning
    GC_TUNING = {
        "low_traffic": {
            "threshold0": 700,   # Collect at 700 new objects
            "threshold1": 10,    # Collect gen1 at 10:1 ratio
            "threshold2": 10,
        },
        "high_traffic": {
            "threshold0": 3000,  # More objects before collection
            "threshold1": 5,     # Collect more often
            "threshold2": 5,
        },
    }

    @staticmethod
    def tune_garbage_collection(workload: str) -> None:
        """Tune GC for workload characteristics"""
        import gc

        thresholds = MemoryOptimizer.GC_TUNING[workload]
        gc.set_threshold(
            thresholds["threshold0"],
            thresholds["threshold1"],
            thresholds["threshold2"]
        )

5.2 Connection Pooling

class ConnectionPoolOptimizer:
    """Configure optimal connection pool parameters"""

    # Pool size calculation
    POOL_SIZING_FORMULA = """
    min_size = (cpu_cores * 2) + spare_connections
    max_size = (cpu_cores * 4) + spare_connections

    Example: 8-core server
    min_size = (8 * 2) + 2 = 18
    max_size = (8 * 4) + 2 = 34

    Why:
    - min_size: Keep connections warm for frequent use
    - max_size: Handle traffic spikes without exhaustion
    """

    class PoolConfig:
        def __init__(
            self,
            cpu_cores: int,
            min_size: int | None = None,
            max_size: int | None = None,
            connection_timeout_ms: int = 5000,
            idle_timeout_ms: int = 60000,
        ):
            self.min_size = min_size or (cpu_cores * 2) + 2
            self.max_size = max_size or (cpu_cores * 4) + 2
            self.connection_timeout_ms = connection_timeout_ms
            self.idle_timeout_ms = idle_timeout_ms

    # Monitor pool health
    @staticmethod
    async def monitor_pool_health(pool) -> dict[str, Any]:
        """Check connection pool metrics"""

        return {
            "connections_open": pool.size,
            "connections_idle": pool.idle_size,
            "connections_busy": pool.size - pool.idle_size,
            "wait_queue_depth": pool.waiting_requests,
            "avg_wait_time_ms": pool.avg_checkout_time_ms,
            "pool_exhaustion_events": pool.exhaustion_count,
        }

6. Monitoring & Profiling

6.1 Query Profiling

class QueryProfiler:
    """Profile query execution for optimization"""

    async def profile_query(
        self,
        query: str,
        params: list | None = None
    ) -> QueryProfile:
        """Profile query execution

        Collects:
        - Execution time breakdown
        - Row count at each step
        - Index usage
        - Memory allocated
        """
        # Get execution plan
        plan = await self.db.query_one(
            f"EXPLAIN (ANALYZE, BUFFERS, TIMING) {query}",
            params or []
        )

        # Parse plan into profile
        profile = self._parse_plan(plan)

        return profile

    def _parse_plan(self, plan: dict) -> QueryProfile:
        """Parse database execution plan into profile"""

        profile = QueryProfile()

        for step in plan["Plan"]:
            profile.add_step(
                name=step["Node Type"],
                rows_output=step["Actual Rows"],
                execution_time_ms=step["Actual Time"][1] - step["Actual Time"][0],
                buffers_hit=step["Shared Hit Blocks"],
                buffers_read=step["Shared Read Blocks"],
            )

        return profile


class QueryProfile:
    """Results of query profiling"""

    def __init__(self):
        self.steps: list[ExecutionStep] = []
        self.total_time_ms: float = 0

    def add_step(self, **kwargs):
        self.steps.append(ExecutionStep(**kwargs))
        self.total_time_ms += kwargs.get("execution_time_ms", 0)

    def get_bottleneck(self) -> ExecutionStep | None:
        """Find slowest step"""
        if not self.steps:
            return None
        return max(self.steps, key=lambda s: s.execution_time_ms)

    def get_index_usage(self) -> dict[str, bool]:
        """Check which indexes are actually used"""
        return {
            step.name: "Index" in step.name
            for step in self.steps
        }

6.2 Distributed Tracing for Performance

class QueryTracer:
    """Trace query execution across services"""

    async def trace_query(
        self,
        query_name: str,
        variables: dict,
        user_context
    ) -> Trace:
        """Execute query with full tracing"""

        with self._create_span(f"query:{query_name}") as root_span:
            root_span.set_attribute("user_id", user_context.user_id)
            root_span.set_attribute("variables", json.dumps(variables))

            with self._create_span("authorization"):
                # Authorization checks
                pass

            with self._create_span("database.prepare"):
                # SQL generation and parameter binding
                pass

            with self._create_span("database.execute"):
                # Execute database query
                pass

            with self._create_span("response.build"):
                # Build response object
                pass

            return self._collect_trace(root_span)

    def _create_span(self, name: str):
        """Create tracing span"""
        # Implementation: OpenTelemetry or similar
        pass

7. Emergency Procedures

7.1 Circuit Breaker Pattern

class CircuitBreaker:
    """Prevent cascading failures with circuit breaker"""

    class State(Enum):
        CLOSED = "closed"      # Normal operation
        OPEN = "open"          # Failing - reject requests
        HALF_OPEN = "half_open"  # Testing recovery

    def __init__(
        self,
        failure_threshold: int = 5,
        recovery_timeout_seconds: int = 60
    ):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout_seconds
        self.state = CircuitBreaker.State.CLOSED
        self.failure_count = 0
        self.last_failure_time = None

    async def call(self, fn, *args, **kwargs) -> Any:
        """Execute function with circuit breaker protection"""

        if self.state == CircuitBreaker.State.OPEN:
            # Check if recovery period elapsed
            if self._recovery_timeout_elapsed():
                self.state = CircuitBreaker.State.HALF_OPEN
                self.failure_count = 0
            else:
                raise CircuitBreakerOpenError(
                    f"Circuit breaker open, retry in "
                    f"{self._time_to_retry_seconds()}s"
                )

        try:
            result = await fn(*args, **kwargs)

            # Success - reset state
            if self.state == CircuitBreaker.State.HALF_OPEN:
                self.state = CircuitBreaker.State.CLOSED
            self.failure_count = 0

            return result

        except Exception as e:
            self.failure_count += 1
            self.last_failure_time = time.time()

            if self.failure_count >= self.failure_threshold:
                self.state = CircuitBreaker.State.OPEN

            raise

    def _recovery_timeout_elapsed(self) -> bool:
        return (
            self.last_failure_time and
            time.time() - self.last_failure_time > self.recovery_timeout
        )

7.2 Graceful Degradation

class GracefulDegradation:
    """Degrade service gracefully under load"""

    async def execute_with_degradation(
        self,
        query: str,
        user_context,
        system_load: float
    ) -> QueryResult:
        """Execute query with degradation based on system load

        Load levels:
        - < 50%: Full quality (all features)
        - 50-75%: Reduced features (skip non-critical optimization)
        - 75-90%: Basic service (minimal features)
        - > 90%: Emergency (cached responses only)
        """

        if system_load > 0.90:
            # Emergency mode: serve cached response
            return await self._get_cached_response(query, user_context)

        elif system_load > 0.75:
            # Degraded: skip expensive operations
            return await self._execute_degraded(query, user_context)

        elif system_load > 0.50:
            # Reduced: some optimizations disabled
            return await self._execute_reduced(query, user_context)

        else:
            # Normal: full execution
            return await self._execute_full(query, user_context)

    async def _get_cached_response(self, query, context):
        """Serve last known good response"""
        # Cache policy: Keep responses for 30 seconds
        key = f"cached_response:{query}:{context.user_id}"
        return await self.cache.get(key)

7.3 Backpressure Handling

class BackpressureManager:
    """Handle traffic surge with graceful backpressure"""

    def __init__(self, max_queue_depth: int = 1000):
        self.request_queue = asyncio.Queue(maxsize=max_queue_depth)
        self.processing_workers = []

    async def handle_request(self, request) -> Response:
        """Queue request with backpressure"""

        try:
            # Try to queue request (fail fast if full)
            self.request_queue.put_nowait(request)
        except asyncio.QueueFull:
            # Queue is full - return backpressure response
            return Response(
                status=503,  # Service Unavailable
                error="E_BACKPRESSURE",
                message="Server is overloaded, please retry in a few seconds",
                retry_after_seconds=random.uniform(1, 5)  # Exponential backoff
            )

        # Process request
        return await self._process_request(request)

    async def _process_request(self, request):
        """Process single request"""
        # Implementation
        pass

8. Performance Optimization Checklist

Database Layer

  • Indexes on all WHERE/JOIN/ORDER BY columns
  • Composite indexes for common filter combinations
  • Partial indexes for soft-deleted rows (WHERE deleted_at IS NULL)
  • Covering indexes for common queries
  • Table statistics up to date (ANALYZE)
  • Partitioning for large tables (time-series)
  • Materialized views for complex aggregations
  • Connection pooling configured
  • Slow query log enabled and monitored

Query Layer

  • Query execution plans analyzed
  • Predicates pushed to database
  • Join order optimized
  • N+1 queries eliminated
  • Query result caching
  • Cache TTL tuning
  • Pagination for large result sets
  • Parallel query execution where possible
  • Query timeout configured

Caching Layer

  • L1 in-memory cache for hot data
  • L2 Redis cache for distributed caching
  • Cache invalidation strategy defined
  • TTL values tuned for workload
  • Hot key detection and mitigation
  • Thundering herd prevention
  • Cache hit rate monitored (> 80% target)
  • Eviction policy appropriate for workload

Infrastructure

  • Connection pool sized for CPU cores
  • Garbage collection tuned
  • Memory usage monitored
  • Multi-instance load balancing
  • Read replica configuration
  • Circuit breakers for external services
  • Backpressure handling
  • Graceful degradation implemented

Monitoring

  • Query latency percentiles (p50, p95, p99)
  • Cache hit/miss rates
  • Database connection pool status
  • Error rates by type
  • System load trending
  • Slow queries identified and optimized
  • Distributed traces for complex operations
  • Alerting thresholds defined

Summary

FraiseQL advanced optimization covers:

✅ Query Optimization

  • Execution plan analysis and adaptation
  • Predicate pushdown to database
  • Join order optimization
  • Index effectiveness

✅ Database Tuning

  • Index design strategy (B-tree, GIN, GIST)
  • Materialized views for aggregations
  • Partitioning for large tables
  • Statistics and query planning

✅ Caching Edge Cases

  • Hot key detection and mitigation
  • Thundering herd prevention
  • Eviction policy selection
  • Cache consistency

✅ Multi-Instance Scaling

  • Replica consistency management
  • Session affinity routing
  • Load balancing strategies
  • Connection locality

✅ Resource Optimization

  • Memory profiling and GC tuning
  • Connection pool configuration
  • Query profiling
  • Distributed tracing

✅ Emergency Procedures

  • Circuit breaker pattern
  • Graceful degradation under load
  • Backpressure handling
  • Cache fallback strategies

Because FraiseQL builds its schema in memory at startup and serves it over FastAPI, the bulk of this tuning lives in PostgreSQL (indexes, views, partitioning, statistics) and in your deployment infrastructure (caching, connection pools, replicas) rather than in the framework itself. Profile with EXPLAIN (ANALYZE, BUFFERS), index for your real query shapes, and apply the caching and scaling patterns above as your workload grows.