Async AI Backend Python: asyncio Patterns, Connection Pools, and Backpressure for 2026

TL;DR — Async AI backend Python: asyncio, connection pools, backpressure. LLM Best Practices: "FastAPI single-threaded async event loop. One blocking call stalls every request. async def for all I/O routes. httpx.AsyncClient shared across requests. asyncio.to_thread for sync libraries. Semaphore per downstream service." Pratik Pathak: "One thread running event loop. Blocking function freezes every request. Configure robust connection pool — async worker handles thousands of concurrent requests." Markaicode: "Read-release-call-reacquire-write pattern. pool_size=10, max_overflow=20, pool_timeout=10, pool_pre_ping. engine.dispose() on shutdown." TutorialQ: "Semaphore + pool timeouts + 503 rejection. 10,000+ concurrent on 4-8 vCPU. Three backpressure signals: queue depth, P99 latency, memory." CallSphere: "httpx.AsyncClient with limits, http2=True. Scope to app lifetime. DNS caching saves 5-50ms. HTTP/2 multiplexes over single TCP." Learn more with FastAPI AI tutorial, streaming SSE, Ollama REST API, and Docker AI API.

LLM Best Practices frames the core principle: "FastAPI runs on a single-threaded async event loop. Every async def route shares that loop. One blocking call in one route stalls every other in-flight request until it returns. Keeping the full request path async is not optional; it is the performance contract of the framework."

Pratik Pathak adds the production reality: "The single most important concept to grasp with asynchronous Python is that you have exactly one thread running the event loop. If any function blocks that loop for even a fraction of a second, every other incoming request will freeze. It is like a single-lane highway where one broken-down car halts miles of traffic."

Async AI Backend Architecture

flowchart TD subgraph EventLoop["Async Event Loop (Single Thread)"] Routes["async def Routes
all I/O routes async
shared event loop
one blocking call stalls all"] Blocking["Blocking Call Handling
asyncio.to_thread
run_in_threadpool
process pool for CPU-heavy
plain def runs in thread pool"] end subgraph HTTPPool["HTTP Connection Pool"] httpx["httpx.AsyncClient
shared across requests
limits: max_connections
timeout: connect/read/write/pool
http2=True for multiplexing
DNS caching"] aiohttp["aiohttp TCPConnector
limit, limit_per_host
ttl_dns_cache=300
keepalive_timeout=30
enable_cleanup_closed"] end subgraph DBPool["Database Connection Pool"] Engine["SQLAlchemy AsyncEngine
pool_size=10
max_overflow=20
pool_timeout=10
pool_pre_ping=True
pool_recycle=3600"] Pattern["Read-Release-Call-Write
1. Read with session
2. Close session
3. Call LLM (no DB)
4. Reacquire session
5. Write results"] end subgraph Concurrency["Concurrency Control"] Semaphore["asyncio.Semaphore
cap in-flight LLM calls
match GPU capacity
per downstream service
module level or app.state"] Backpressure["Backpressure
queue depth threshold
503 + Retry-After
P99 latency > 2x baseline
memory > 80% stop accept"] FanOut["Fan-Out Pattern
asyncio.gather
multi-model queries
parallel LLM calls
bounded by semaphore"] end subgraph LLM["LLM Backend"] Ollama["Ollama
localhost:11434
stream=true
async httpx"] OpenAI["OpenAI/Anthropic
AsyncOpenAI
stream.text_stream
http2=True"] end subgraph Lifecycle["Application Lifecycle"] Lifespan["Lifespan Context
create AsyncClient at startup
create engine at startup
dispose on shutdown
engine.dispose()"] Workers["uvicorn Workers
2-4 per core
each worker own event loop
each worker own pool
PgBouncer for multiplexing"] end Routes --> httpx Routes --> Engine Routes --> Semaphore Blocking --> Concurrency httpx --> Ollama httpx --> OpenAI aiohttp --> Ollama Engine --> Pattern Semaphore --> Backpressure Semaphore --> FanOut Lifespan --> httpx Lifespan --> Engine Workers --> Routes style EventLoop fill:#4169E1,color:#fff style HTTPPool fill:#39FF14,color:#000 style DBPool fill:#2D1B69,color:#fff style Concurrency fill:#FF6B6B,color:#fff

Async Pattern Reference

Pattern Purpose Key API Use Case
async def endpoints Non-blocking I/O FastAPI async routes All I/O routes
asyncio.to_thread Offload sync calls asyncio.to_thread(func, args) Sync libraries in async
httpx.AsyncClient HTTP connection pool limits, timeout, http2 LLM API calls
asyncio.Semaphore Concurrency cap Semaphore(N) GPU capacity limit
Read-release-write DB session management db.close() before LLM LLM + database
asyncio.gather Parallel execution gather(*tasks) Multi-model fan-out
Lifespan Resource lifecycle @asynccontextmanager Client/pool init

Implementation

from dataclasses import dataclass
from typing import Optional
from enum import Enum

class AsyncPattern(Enum):
    ENDPOINT = "endpoint"
    BLOCKING = "blocking"
    POOLING = "pooling"
    BACKPRESSURE = "backpressure"

@dataclass
class AsyncAIBackendGuide:
    """Async AI backend Python implementation guide."""

    def get_app_lifespan(self) -> str:
        """FastAPI app with lifespan for resource management."""
        return (
            "# === APP WITH LIFESPAN ===\n"
            "from fastapi import FastAPI\n"
            "from contextlib import asynccontextmanager\n"
            "import httpx, asyncio\n"
            "from sqlalchemy.ext.asyncio\n"
            "    import (\n"
            "    create_async_engine,\n"
            "    AsyncSession,\n"
            "    async_sessionmaker)\n"
            "\n"
            "@asynccontextmanager\n"
            "async def lifespan(app: FastAPI):\n"
            "    '''Manage resources.''' \n"
            "    # Startup\n"
            "    app.state.http_client = (\n"
            "        httpx.AsyncClient(\n"
            "            limits=httpx.Limits(\n"
            "                max_connections=100,\n"
            "                max_keepalive=20),\n"
            "            timeout=httpx.Timeout(\n"
            "                connect=5.0,\n"
            "                read=300.0,\n"
            "                write=10.0,\n"
            "                pool=10.0),\n"
            "            http2=True))\n"
            "    \n"
            "    app.state.engine = (\n"
            "        create_async_engine(\n"
            "            DATABASE_URL,\n"
            "            pool_size=10,\n"
            "            max_overflow=20,\n"
            "            pool_timeout=10,\n"
            "            pool_pre_ping=True,\n"
            "            pool_recycle=3600))\n"
            "    \n"
            "    app.state.semaphore = (\n"
            "        asyncio.Semaphore(4))\n"
            "    \n"
            "    yield\n"
            "    \n"
            "    # Shutdown\n"
            "    await app.state\\\n"
            "        .http_client.aclose()\n"
            "    await app.state\\\n"
            "        .engine.dispose()\n"
            "\n"
            "app = FastAPI(lifespan=lifespan)\n"
            "DATABASE_URL = (\n"
            "    'postgresql+asyncpg://'\n"
            "    'user:pass@localhost/db')"
        )

    def get_async_endpoint(self) -> str:
        """Async endpoint with semaphore and LLM call."""
        return (
            "# === ASYNC ENDPOINT ===\n"
            "from fastapi import Request, HTTPException\n"
            "from sqlalchemy.ext.asyncio\n"
            "    import AsyncSession\n"
            "from sqlalchemy import select\n"
            "\n"
            "@app.post('/v1/generate/{user_id}')\n"
            "async def generate(\n"
            "    user_id: int,\n"
            "    request: Request):\n"
            "    '''Read-release-call-write\n"
            "    pattern for LLM + DB.''' \n"
            "    \n"
            "    # Phase 1: Read\n"
            "    async with (\n"
            "      AsyncSession(\n"
            "        request.app.state.engine)\n"
            "      ) as db:\n"
            "        result = await db.execute(\n"
            "            select(User).where(\n"
            "              User.id == user_id))\n"
            "        user = result.scalar_one()\n"
            "        prompt = user.prompt_template\n"
            "    # Session auto-closed here\n"
            "    \n"
            "    # Phase 2: LLM call\n"
            "    # (no DB connection held)\n"
            "    sem = request.app.state.semaphore\n"
            "    async with sem:\n"
            "        resp = await (\n"
            "          request.app.state\n"
            "          .http_client.post(\n"
            "            'http://localhost:11434'\n"
            "            '/api/chat',\n"
            "            json={\n"
            "                'model': 'llama3.2',\n"
            "                'messages': [{\n"
            "                  'role': 'user',\n"
            "                  'content': prompt}]},\n"
            "            timeout=300.0))\n"
            "        llm_result = resp.json()\n"
            "    \n"
            "    # Phase 3: Write\n"
            "    async with (\n"
            "      AsyncSession(\n"
            "        request.app.state.engine)\n"
            "      ) as db:\n"
            "        log = GenerationLog(\n"
            "            user_id=user_id,\n"
            "            result=llm_result[\n"
            "              'message']['content'])\n"
            "        db.add(log)\n"
            "        await db.commit()\n"
            "    \n"
            "    return {'result':\n"
            "      llm_result['message']['content']}"
        )

    def get_blocking_handling(self) -> str:
        """Handling blocking calls in async context."""
        return (
            "# === BLOCKING CALL HANDLING ===\n"
            "\n"
            "# WRONG: blocks event loop\n"
            "# import requests\n"
            "# @app.post('/bad')\n"
            "# async def bad():\n"
            "#     resp = requests.get(url)\n"
            "#     # ALL other requests freeze\n"
            "#     return resp.json()\n"
            "\n"
            "# RIGHT: async httpx\n"
            "@app.post('/good')\n"
            "async def good(request: Request):\n"
            "    resp = await (\n"
            "      request.app.state\n"
            "      .http_client.get(url))\n"
            "    return resp.json()\n"
            "\n"
            "# RIGHT: asyncio.to_thread\n"
            "# for sync libraries\n"
            "import time\n"
            "\n"
            "@app.post('/sleep')\n"
            "async def sleep_endpoint():\n"
            "    # WRONG: time.sleep(5)\n"
            "    # blocks entire event loop\n"
            "    \n"
            "    # RIGHT: asyncio.sleep\n"
            "    await asyncio.sleep(5)\n"
            "    return {'slept': 5}\n"
            "\n"
            "# Sync library in async\n"
            "def sync_heavy_compute(data):\n"
            "    # CPU-bound work\n"
            "    import json\n"
            "    return json.dumps(data)\n"
            "\n"
            "@app.post('/compute')\n"
            "async def compute(data: dict):\n"
            "    # Offload to thread pool\n"
            "    result = await (\n"
            "      asyncio.to_thread(\n"
            "        sync_heavy_compute, data))\n"
            "    return {'result': result}\n"
            "\n"
            "# Plain def runs in thread pool\n"
            "# by FastAPI design\n"
            "@app.get('/sync-route')\n"
            "def sync_route():\n"
            "    # Safe: runs in thread\n"
            "    # not on event loop\n"
            "    return {'ok': True}"
        )

    def get_backpressure(self) -> str:
        """Backpressure with semaphore and 503 rejection."""
        return (
            "# === BACKPRESSURE ===\n"
            "\n"
            "from fastapi.responses import JSONResponse\n"
            "\n"
            "@app.post('/v1/chat')\n"
            "async def chat_with_backpressure(\n"
            "    request: Request):\n"
            "    '''Backpressure with\n"
            "    semaphore and 503.''' \n"
            "    \n"
            "    sem = request.app.state.semaphore\n"
            "    \n"
            "    # Check queue depth\n"
            "    if sem._value == 0:\n"
            "        # All slots in use\n"
            "        return JSONResponse(\n"
            "            status_code=503,\n"
            "            content={\n"
            "              'error': 'overloaded'},\n"
            "            headers={\n"
            "              'Retry-After': '5'})\n"
            "    \n"
            "    async with sem:\n"
            "        try:\n"
            "            resp = await (\n"
            "              request.app.state\n"
            "              .http_client.post(\n"
            "                'http://'\n"
            "                'localhost:11434'\n"
            "                '/api/chat',\n"
            "                json={\n"
            "                  'model': 'llama3.2',\n"
            "                  'messages': [\n"
            "                    {'role': 'user',\n"
            "                     'content': 'Hi'}]},\n"
            "                timeout=300.0))\n"
            "            return resp.json()\n"
            "        except httpx.PoolTimeout:\n"
            "            return JSONResponse(\n"
            "                status_code=503,\n"
            "                content={\n"
            "                  'error': 'timeout'},\n"
            "                headers={\n"
            "                  'Retry-After': '10'})\n"
            "        except httpx.ReadTimeout:\n"
            "            return JSONResponse(\n"
            "                status_code=504,\n"
            "                content={\n"
            "                  'error': 'gateway'\n"
            "                  ' timeout'})"
        )

    def get_fan_out(self) -> str:
        """Fan-out pattern for multi-model queries."""
        return (
            "# === FAN-OUT PATTERN ===\n"
            "\n"
            "@app.post('/v1/multi-model')\n"
            "async def multi_model(\n"
            "    request: Request,\n"
            "    prompt: str):\n"
            "    '''Query multiple models\n"
            "    in parallel.''' \n"
            "    \n"
            "    sem = request.app.state.semaphore\n"
            "    client = (\n"
            "        request.app.state.http_client)\n"
            "    \n"
            "    async def call_model(model):\n"
            "        async with sem:\n"
            "            resp = await client.post(\n"
            "                'http://'\n"
            "                'localhost:11434'\n"
            "                '/api/chat',\n"
            "                json={\n"
            "                    'model': model,\n"
            "                    'messages': [{\n"
            "                      'role': 'user',\n"
            "                      'content': prompt}]},\n"
            "                timeout=300.0)\n"
            "            return {\n"
            "                'model': model,\n"
            "                'response': resp\n"
            "                  .json()['message']\n"
            "                  ['content']}\n"
            "    \n"
            "    # Parallel execution\n"
            "    results = await asyncio.gather(\n"
            "        call_model('llama3.2'),\n"
            "        call_model('qwen2.5'),\n"
            "        call_model('mistral'),\n"
            "        return_exceptions=True)\n"
            "    \n"
            "    return {'results': [\n"
            "        r for r in results\n"
            "        if not isinstance(\n"
            "          r, Exception)]}"
        )

    def get_production_tips(self) -> dict:
        """Production tips."""
        return {
            "event_loop": "Single thread, one blocking call stalls all. async def for all I/O routes. Never time.sleep, requests.get in async.",
            "to_thread": "asyncio.to_thread for sync libraries. run_in_threadpool from Starlette. Offload CPU-heavy to process pool.",
            "http_client": "Single httpx.AsyncClient shared across requests. Create at startup via lifespan. http2=True for multiplexing.",
            "pool_sizing": "pool_size=10, max_overflow=20, pool_timeout=10. Formula: concurrent x avg DB time / avg total time. PgBouncer for multiplexing.",
            "read_release_write": "Read DB, close session, call LLM, reacquire, write. Never hold DB connection during LLM call.",
            "semaphore": "asyncio.Semaphore matching GPU capacity. Per downstream service. Module level or app.state in lifespan.",
            "backpressure": "503 + Retry-After when overloaded. Three signals: queue depth, P99 latency, memory. Pool timeout for httpx.",
            "workers": "2-4 per core. Each worker own event loop and pool. Size DB pool per worker. PgBouncer for connection multiplexing.",
            "dispose": "engine.dispose() on shutdown. Without it: asyncpg warnings, slow pod shutdown in Kubernetes, failed health checks.",
            "dns_caching": "DNS resolution 5-50ms without caching. httpx and aiohttp can cache. aiohttp: ttl_dns_cache=300.",
        }

Async AI Backend Checklist

  • [ ] FastAPI runs on single-threaded async event loop — one blocking call stalls every in-flight request
  • [ ] async def for all routes that touch database, HTTP client, or cache — not optional, it is the performance contract
  • [ ] Plain def routes run in thread pool by FastAPI design — safe but adds overhead, bypasses async context
  • [ ] Never call time.sleep, requests.get, or sync file reads inside async def — blocks entire event loop
  • [ ] Use await asyncio.sleep() for delays, httpx.AsyncClient for HTTP, asyncpg for database
  • [ ] asyncio.to_thread (Python 3.9+) for sync libraries in async context — preferred spelling
  • [ ] loop.run_in_executor(None, func) equivalent for older code — run_in_threadpool from Starlette
  • [ ] Offload CPU-heavy operations (image processing, cryptography) to process pool
  • [ ] httpx.AsyncClient: shared across requests, create at startup via lifespan, close on shutdown
  • [ ] httpx limits: max_connections=100, max_keepalive=20 — match expected concurrency
  • [ ] httpx timeout: connect=5.0, read=300.0 (LLMs slow), write=10.0, pool=10.0
  • [ ] httpx http2=True for HTTP/2 multiplexing — multiple requests over single TCP connection
  • [ ] Most common mistake: creating new httpx client per request — always scope to app lifetime
  • [ ] aiohttp TCPConnector: limit=100, limit_per_host=30, ttl_dns_cache=300, keepalive_timeout=30
  • [ ] DNS resolution adds 5-50ms without caching — both httpx and aiohttp can cache
  • [ ] SQLAlchemy AsyncEngine: pool_size=10, max_overflow=20, pool_timeout=10, pool_pre_ping=True, pool_recycle=3600
  • [ ] pool_timeout=10: fail fast — don't let requests queue for 30s, signal to increase pool_size
  • [ ] pool_pre_ping=True: checks connection health before use, catches stale connections
  • [ ] Read-release-call-write pattern: read DB, close session, call LLM (2-8s), reacquire, write
  • [ ] Never hold DB connection during LLM call — exhausts pool when LLM calls hold connections open
  • [ ] engine.dispose() on shutdown — without it: asyncpg warnings, slow pod shutdown, failed health checks
  • [ ] asyncio.Semaphore for concurrency cap — match GPU capacity (RTX 4090: 2-4 concurrent 7B streams)
  • [ ] Declare semaphore at module level or attach to app.state in lifespan
  • [ ] Semaphore per downstream service for independent tuning
  • [ ] Backpressure: 503 + Retry-After when overloaded — three signals: queue depth, P99 latency, memory
  • [ ] Backpressure signal 1: semaphore queue depth > threshold → reject with 503 + Retry-After
  • [ ] Backpressure signal 2: backend P99 latency > 2x baseline → reduce admission rate
  • [ ] Backpressure signal 3: system memory > 80% → stop accepting connections
  • [ ] httpx.PoolTimeout when pool exhausted — handle by increasing pool size or implementing queuing
  • [ ] Fan-out pattern: asyncio.gather for parallel multi-model queries — bounded by semaphore
  • [ ] Lifespan context: create AsyncClient, engine, semaphore at startup; close/dispose on shutdown
  • [ ] uvicorn workers: 2-4 per core — each worker own event loop and own connection pool
  • [ ] Size DB pool per worker: pool_size=10 x 4 workers = 40 DB connections — use PgBouncer to multiplex
  • [ ] FastAPI gateways handle 10,000+ concurrent connections on 4-8 vCPU — memory 200-500 MB
  • [ ] Asyncio for gateway (I/O-bound), threading for CPU-intensive preprocessing if >10ms blocking
  • [ ] Never use threading for HTTP forwarding — asyncio is 10x more memory-efficient
  • [ ] BackgroundTasks for lightweight non-blocking tasks — Arq or Celery for heavy CPU-bound jobs
  • [ ] Read FastAPI AI tutorial for base setup
  • [ ] Read streaming SSE for streaming patterns
  • [ ] Read Ollama REST API for LLM API
  • [ ] Read Docker AI API for deployment
  • [ ] Test: no blocking calls in async endpoints — all requests respond concurrently
  • [ ] Test: semaphore limits concurrency to GPU capacity under load
  • [ ] Test: 503 returned when overloaded with Retry-After header
  • [ ] Test: read-release-write pattern releases DB connections during LLM calls
  • [ ] Test: connection pool doesn't exhaust under concurrent requests
  • [ ] Test: engine.dispose() cleans up connections on shutdown
  • [ ] Test: fan-out parallel queries complete faster than sequential
  • [ ] Test: httpx client shared across requests (not created per request)
  • [ ] Document event loop rules, pool config, semaphore capacity, backpressure signals, lifespan management

FAQ

How do you build an async AI backend with Python and FastAPI?

Use async def endpoints, httpx.AsyncClient for LLM calls, and asyncio primitives for concurrency control. LLM Best Practices: "FastAPI runs on single-threaded async event loop. Every async def route shares that loop. One blocking call stalls every other in-flight request. Keeping full request path async is not optional — it is the performance contract. Declare every route async def when it touches database, HTTP client, or cache. httpx.AsyncClient mirrors requests API. Single AsyncClient instance shared across requests — create at startup via lifespan." Pratik Pathak: "Single most important concept: exactly one thread running event loop. If any function blocks that loop, every other incoming request freezes. Like single-lane highway where one broken-down car halts miles of traffic." Building: (1) async def endpoints. (2) httpx.AsyncClient for LLM calls. (3) asyncio.Semaphore for concurrency. (4) Async database with SQLAlchemy + asyncpg. (5) Lifespan for client/pool initialization. (6) Never block event loop.

How do you handle blocking calls in async FastAPI endpoints?

Use asyncio.to_thread for synchronous libraries, never call blocking functions directly in async def. LLM Best Practices: "asyncio.to_thread (Python 3.9+) is preferred. loop.run_in_executor(None, func) for older code. FastAPI re-exports run_in_threadpool from Starlette. Never call time.sleep, requests.get, or synchronous file reads directly inside async def route. time.sleep(n) inside async def blocks entire event loop for n seconds — all concurrent requests queue behind it. Use await asyncio.sleep(n) for delays and httpx.AsyncClient for outbound HTTP." Pratik Pathak: "Blocking I/O: using synchronous libraries like requests or urllib inside async endpoint — blocks thread while waiting. Heavy CPU-bound: image processing, cryptography directly in endpoint. Offload to thread or process pool using asyncio loop executor." Handling: (1) asyncio.to_thread(sync_func, args) for sync libraries. (2) await asyncio.sleep() not time.sleep(). (3) httpx.AsyncClient not requests. (4) run_in_threadpool from Starlette. (5) Offload CPU-heavy to process pool. (6) Plain def routes run in thread pool by FastAPI design.

How do you configure connection pooling for async AI backends?

Use httpx.AsyncClient with limits for HTTP, SQLAlchemy AsyncEngine with pool_size for database, and scope clients to application lifetime. CallSphere: "Connection pooling reuses established TCP connections. httpx.AsyncClient with limits, timeout, http2=True. Most common mistake: creating new client per request. Always scope client to application lifetime. aiohttp TCPConnector with limit, limit_per_host, ttl_dns_cache. DNS resolution adds 5-50ms without caching. HTTP/2 multiplexes requests over single TCP connection. Pool timeout: requests wait in queue until connection available, httpx.PoolTimeout if expired." Markaicode: "create_async_engine with pool_size=10, max_overflow=20, pool_timeout=10, pool_pre_ping=True, pool_recycle=3600. LLM pattern: read, release session, call LLM, reacquire, write. engine.dispose() on shutdown." Pool config: (1) httpx: limits=max_connections, max_keepalive, timeout. (2) SQLAlchemy: pool_size, max_overflow, pool_timeout, pool_pre_ping. (3) aiohttp: TCPConnector limit, limit_per_host, DNS cache. (4) Scope to app lifetime via lifespan. (5) HTTP/2 for multiplexing. (6) Pool sizing: concurrent requests x avg DB time / avg total time.

How do you implement backpressure in async AI backends?

Use asyncio.Semaphore to cap in-flight LLM requests, pool timeouts, and 503 rejection with Retry-After. TutorialQ: "Bounded concurrency with semaphores. Most common production issue: connection pool exhaustion. When backend engines slow down, pending requests accumulate. Without pool timeouts and backpressure, gateway accumulates thousands of open connections, exhausting file descriptors and crashing. Fix: bounded concurrency (semaphore), pool timeouts (httpx Limits), explicit rejection (503 with Retry-After). Anyscale, Modal, Replicate run FastAPI gateways handling 10,000+ concurrent connections on 4-8 vCPU. Memory footprint 200-500 MB. Three backpressure signals: (1) semaphore queue depth > threshold → 503 + Retry-After, (2) backend P99 latency > 2x baseline → reduce admission, (3) system memory > 80% → stop accepting." LLM Best Practices: "Declare semaphore at module level or attach to app.state in lifespan. Semaphore per downstream service for independent tuning." Backpressure: (1) asyncio.Semaphore(N) matching GPU capacity. (2) httpx Limits for pool timeouts. (3) 503 + Retry-After when overloaded. (4) Queue depth threshold. (5) P99 latency monitoring. (6) Memory limit check.

How do you manage async database sessions with LLM calls in FastAPI?

Use the read-release-call-reacquire-write pattern to avoid holding DB connections during LLM calls. Markaicode: "Split endpoint into three phases: read, release, call LLM, reacquire, write. Phase 1: read what you need, close session. Phase 2: LLM call — no DB connection held during 2-8 second wait. Phase 3: reacquire connection to write results. Why: LLM calls hold connections open for seconds, exhausting pool. pool_size=10, max_overflow=20, pool_timeout=10 (fail fast), pool_pre_ping=True, pool_recycle=3600. engine.dispose() on shutdown — without it, asyncpg warnings about unclosed connections, slow pod shutdown in Kubernetes." Pratik Pathak: "One worker process can handle thousands of concurrent requests. If each opens raw DB connection, database tips over with too many connections. Configure robust connection pool with SQLAlchemy and asyncpg." Pattern: (1) Read with async session. (2) Close session before LLM call. (3) Call LLM without DB connection. (4) Reacquire session for write. (5) pool_timeout=10 to fail fast. (6) pool_pre_ping for stale connections. (7) engine.dispose() on shutdown.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →