2020import asyncpg
2121from contextlib import asynccontextmanager
2222from typing import AsyncGenerator , Optional
23- from fastapi import HTTPException
23+ from fastapi import HTTPException , status
2424
2525from core .configuration import load_environment
2626
@@ -66,16 +66,24 @@ async def init_db_pool():
6666 num_workers = int (os .getenv ("WEB_CONCURRENCY" , "1" if env_state == "development" else "4" ))
6767
6868 # Calculate per-worker sizes to avoid exceeding PostgreSQL limits
69- # Increased pool size to handle concurrent JWT token requests and other operations
70- # Target: ~90 total connections across all workers (leaves room for other services)
69+ # Increased pool size to handle high concurrent JWT token requests
70+ # After optimization: each token request uses 1 connection (was 2)
71+ # Target: support 100+ concurrent requests safely
7172 if env_state == "development" :
7273 min_size = 0 # Lazy init - no connections at startup
7374 max_size = 10
7475 else :
7576 min_size = 2 # Keep a few connections ready for fast JWT validation
76- # With 6 workers: 15 per worker = 90 total (safe, PostgreSQL default max is 100)
77- # Increased from 8 to 15 to handle bursts of concurrent requests
78- max_size = max (10 , min (15 , 90 // num_workers ))
77+ # Calculate pool size to support high concurrency
78+ # Target: 100+ concurrent requests across all workers
79+ # Formula: ensure at least 20-30 connections per worker, but cap at reasonable limit
80+ # Allow environment override via DB_POOL_MAX_SIZE
81+ if os .getenv ("DB_POOL_MAX_SIZE" ):
82+ max_size = int (os .getenv ("DB_POOL_MAX_SIZE" ))
83+ else :
84+ # Default: 25 per worker (supports 100 concurrent with 4 workers, 150 with 6)
85+ # PostgreSQL default max_connections is usually 100, but can be increased
86+ max_size = 25
7987
8088 print (f"[DB POOL INIT] Workers: { num_workers } , min={ min_size } , max={ max_size } per worker" )
8189 print (f"[DB POOL INIT] Total potential: { max_size * num_workers } connections" )
@@ -280,6 +288,13 @@ async def _insert_logic(connection):
280288 # Manage our own connection
281289 async with get_db_connection () as connection :
282290 return await _insert_logic (connection )
291+ except asyncpg .exceptions .UniqueViolationError as e :
292+ # Handle unique constraint violations (e.g., duplicate email) atomically
293+ logger .warning (f"Registration attempt with duplicate email { email } : { str (e )} " )
294+ raise HTTPException (
295+ status_code = status .HTTP_400_BAD_REQUEST ,
296+ detail = "A user with that email already exists"
297+ )
283298 except Exception as e :
284299 logger .error (f"Error inserting data for user { email } : { str (e )} " )
285300 raise HTTPException (status_code = 400 , detail = str (e ))
@@ -331,10 +346,12 @@ async def select_scope_id(conn: Optional[asyncpg.Connection] = None) -> Optional
331346 raise HTTPException (status_code = 400 , detail = str (e ))
332347
333348
334- async def get_scopes_by_user (user_id : int ):
349+ async def get_scopes_by_user (user_id : int , conn : Optional [ asyncpg . Connection ] = None ):
335350 """
336351 Get all scope names assigned to a user.
337352 Returns a list of scope names.
353+ If conn is provided, uses that connection (caller manages it).
354+ Otherwise, manages its own connection.
338355 """
339356 query = f"""
340357 SELECT s.name
@@ -344,11 +361,19 @@ async def get_scopes_by_user(user_id: int):
344361 """
345362
346363 try :
347- async with get_db_connection () as conn :
364+ if conn is not None :
365+ # Use provided connection
348366 results = await conn .fetch (query , user_id )
349367 assigned_scopes_to_user = [result ["name" ] for result in results ]
350368 logger .debug (f"Scopes for user { user_id } : { assigned_scopes_to_user } " )
351369 return assigned_scopes_to_user
370+ else :
371+ # Manage our own connection
372+ async with get_db_connection () as connection :
373+ results = await connection .fetch (query , user_id )
374+ assigned_scopes_to_user = [result ["name" ] for result in results ]
375+ logger .debug (f"Scopes for user { user_id } : { assigned_scopes_to_user } " )
376+ return assigned_scopes_to_user
352377 except Exception as e :
353378 logger .error (f"Error getting scopes for user { user_id } : { str (e )} " )
354379 raise HTTPException (status_code = 400 , detail = str (e ))
0 commit comments