Soumik Bose commited on
Commit ·
2f2ca6a
1
Parent(s): 615412d
manage
Browse files- controller.py +54 -17
controller.py
CHANGED
|
@@ -5,11 +5,13 @@ import time
|
|
| 5 |
import uuid
|
| 6 |
import os
|
| 7 |
import re
|
| 8 |
-
import asyncio
|
| 9 |
-
import multiprocessing
|
| 10 |
-
import hashlib
|
| 11 |
-
import json
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# --- FastAPI & Core ---
|
| 15 |
from fastapi import FastAPI, HTTPException, Depends
|
|
@@ -17,20 +19,18 @@ from fastapi.encoders import jsonable_encoder
|
|
| 17 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 18 |
from fastapi.middleware.cors import CORSMiddleware
|
| 19 |
from starlette.concurrency import run_in_threadpool
|
| 20 |
-
from anyio import to_thread
|
| 21 |
from dotenv import load_dotenv
|
| 22 |
from pydantic import BaseModel
|
| 23 |
|
| 24 |
-
# --- Database Drivers ---
|
| 25 |
from bson import ObjectId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
from psycopg2.extras import RealDictCursor
|
| 27 |
-
from
|
| 28 |
-
|
| 29 |
-
# --- Database Pool ---
|
| 30 |
-
from mysql.connector import pooling
|
| 31 |
-
from psycopg2 import pool as pg_pool
|
| 32 |
-
from threading import Lock
|
| 33 |
-
|
| 34 |
import uvicorn
|
| 35 |
|
| 36 |
# --- Existing Services ---
|
|
@@ -53,12 +53,49 @@ logging.basicConfig(
|
|
| 53 |
)
|
| 54 |
logger = logging.getLogger("API_Controller")
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
@asynccontextmanager
|
| 57 |
async def lifespan(app: FastAPI):
|
| 58 |
-
# Startup logic
|
| 59 |
-
|
| 60 |
-
|
|
|
|
| 61 |
yield
|
|
|
|
| 62 |
|
| 63 |
app = FastAPI(
|
| 64 |
title="Unified Data Executor API (Mongo, SQL, CSV)",
|
|
|
|
| 5 |
import uuid
|
| 6 |
import os
|
| 7 |
import re
|
| 8 |
+
import asyncio # Added for parallel batching
|
| 9 |
+
import multiprocessing # Added for worker calculation
|
| 10 |
+
import hashlib # Added for Coalescing
|
| 11 |
+
import json # Added for Coalescing
|
| 12 |
+
import psutil # Added for Dynamic RAM calculation
|
| 13 |
+
from typing import List, Optional, Dict, Any, TypeVar, Generic # Added Generic/TypeVar
|
| 14 |
+
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
| 15 |
|
| 16 |
# --- FastAPI & Core ---
|
| 17 |
from fastapi import FastAPI, HTTPException, Depends
|
|
|
|
| 19 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 20 |
from fastapi.middleware.cors import CORSMiddleware
|
| 21 |
from starlette.concurrency import run_in_threadpool
|
| 22 |
+
from anyio import to_thread # Added for AnyIO 4.x concurrency tuning
|
| 23 |
from dotenv import load_dotenv
|
| 24 |
from pydantic import BaseModel
|
| 25 |
|
| 26 |
+
# --- Database Drivers & Pooling ---
|
| 27 |
from bson import ObjectId
|
| 28 |
+
import mysql.connector
|
| 29 |
+
from mysql.connector import pooling # For MySQL Pooling
|
| 30 |
+
import psycopg2
|
| 31 |
+
from psycopg2 import pool as pg_pool # For Postgres Pooling
|
| 32 |
from psycopg2.extras import RealDictCursor
|
| 33 |
+
from threading import Lock # To handle concurrency safely
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
import uvicorn
|
| 35 |
|
| 36 |
# --- Existing Services ---
|
|
|
|
| 53 |
)
|
| 54 |
logger = logging.getLogger("API_Controller")
|
| 55 |
|
| 56 |
+
def get_dynamic_thread_limit():
|
| 57 |
+
"""
|
| 58 |
+
Calculates a safe thread limit based on available RAM and CPU cores.
|
| 59 |
+
Returns an integer safe for to_thread.current_default_thread_limiter().total_tokens
|
| 60 |
+
"""
|
| 61 |
+
try:
|
| 62 |
+
# 1. Get System Resources
|
| 63 |
+
total_ram_bytes = psutil.virtual_memory().total
|
| 64 |
+
total_cores = multiprocessing.cpu_count()
|
| 65 |
+
|
| 66 |
+
# 2. Identify how many Uvicorn Workers are running
|
| 67 |
+
# Assuming production setup uses: max(1, total_cores - 2)
|
| 68 |
+
num_workers = max(1, total_cores - 2)
|
| 69 |
+
|
| 70 |
+
# 3. Calculate RAM available PER WORKER
|
| 71 |
+
ram_per_worker = total_ram_bytes / num_workers
|
| 72 |
+
|
| 73 |
+
# 4. Reserve Buffer (Keep 30% for OS/Python overhead, use 70% for threads)
|
| 74 |
+
safe_ram_pool = ram_per_worker * 0.70
|
| 75 |
+
|
| 76 |
+
# 5. Estimate Thread Cost (~8MB conservative safety margin)
|
| 77 |
+
BYTES_PER_THREAD = 8 * 1024 * 1024
|
| 78 |
+
|
| 79 |
+
calculated_limit = int(safe_ram_pool / BYTES_PER_THREAD)
|
| 80 |
+
|
| 81 |
+
# 6. Apply Reasonable Hard Caps (Min 100, Max 3000)
|
| 82 |
+
final_limit = max(100, min(calculated_limit, 3000))
|
| 83 |
+
|
| 84 |
+
logger.info(f"Dynamic Limit Config: {total_ram_bytes/(1024**3):.2f}GB RAM / {num_workers} Workers. Limit: {final_limit}")
|
| 85 |
+
return final_limit
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
logger.warning(f"Failed to calculate dynamic threads ({e}). Fallback to 1000.")
|
| 89 |
+
return 1000
|
| 90 |
+
|
| 91 |
@asynccontextmanager
|
| 92 |
async def lifespan(app: FastAPI):
|
| 93 |
+
# Startup logic: Calculate and set dynamic thread limit
|
| 94 |
+
safe_limit = get_dynamic_thread_limit()
|
| 95 |
+
to_thread.current_default_thread_limiter().total_tokens = safe_limit
|
| 96 |
+
logger.info(f"Worker Process Started: Thread pool capacity set to {safe_limit}.")
|
| 97 |
yield
|
| 98 |
+
# Shutdown logic (if any) goes here
|
| 99 |
|
| 100 |
app = FastAPI(
|
| 101 |
title="Unified Data Executor API (Mongo, SQL, CSV)",
|