Soumik Bose commited on
Commit
2f2ca6a
·
1 Parent(s): 615412d
Files changed (1) hide show
  1. 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
- from typing import List, Optional, Dict, Any, TypeVar, Generic
 
 
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 urllib.parse import urlparse, parse_qs, urlencode, urlunparse
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
- to_thread.current_default_thread_limiter().total_tokens = 2000
60
- logger.info("Worker Process Started: Thread pool capacity set to 2000.")
 
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)",