Skip to content

Commit 221638c

Browse files
Kamal Sai DevarapalliKamal Sai Devarapalli
authored andcommitted
Configure Gunicorn for high-performance (1000-2000 req/sec)
- Added Gunicorn configuration files for all services (4 workers, 2 threads) - Created WSGI entry points for all services - Updated Dockerfiles to use Gunicorn instead of Flask dev server - Added GUNICORN_WORKERS and GUNICORN_THREADS environment variables - Added performance documentation (PERFORMANCE_CONFIG.md, GUNICORN_HIERARCHY.md) - Each service now handles 8 concurrent requests (4 workers × 2 threads) - Target capacity: 1000-2000 requests/second per instance
1 parent 3a189a2 commit 221638c

File tree

15 files changed

+793
-4
lines changed

15 files changed

+793
-4
lines changed

GUNICORN_HIERARCHY.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
# Gunicorn Architecture Hierarchy
2+
## Understanding Workers, Threads, and Connections
3+
4+
## Visual Hierarchy
5+
6+
```
7+
┌─────────────────────────────────────────────────────────────────┐
8+
│ GUNICORN MASTER PROCESS │
9+
│ (Manages everything, listens on port, handles signals) │
10+
└─────────────────────┬───────────────────────────────────────────┘
11+
12+
│ Forks/Creates
13+
14+
┌─────────────┴─────────────┐
15+
│ │
16+
┌────▼─────┐ ┌─────▼──────┐
17+
│ WORKER 1 │ │ WORKER 2 │ ← Separate Processes
18+
│ (Process)│ │ (Process) │ Each has own Python
19+
└────┬─────┘ └─────┬──────┘ interpreter & GIL
20+
│ │
21+
│ Creates Threads │ Creates Threads
22+
│ │
23+
┌────┴────┐ ┌────┴─────┐
24+
│ THREAD 1│ │ THREAD 1 │
25+
│ THREAD 2│ │ THREAD 2 │ ← Threads within process
26+
└────┬────┘ └────┬─────┘
27+
│ │
28+
│ Handles Requests │ Handles Requests
29+
│ │
30+
┌────┴──────────────┐ ┌────┴──────────────┐
31+
│ CONNECTION POOL │ │ CONNECTION POOL │
32+
│ (Database/Redis) │ │ (Database/Redis) │
33+
└───────────────────┘ └───────────────────┘
34+
```
35+
36+
## Detailed Breakdown
37+
38+
### Level 1: Gunicorn Master Process
39+
40+
**What it does:**
41+
- Listens on the network port (e.g., 9092)
42+
- Accepts incoming HTTP connections
43+
- Distributes connections to workers
44+
- Manages worker lifecycle (start, restart, kill)
45+
- Handles signals (SIGTERM, SIGHUP, etc.)
46+
47+
**Properties:**
48+
- Single process
49+
- Doesn't handle request processing directly
50+
- Coordinates all workers
51+
52+
### Level 2: Worker Processes
53+
54+
**What they are:**
55+
- Separate OS processes (forked from master)
56+
- Each has its own Python interpreter
57+
- Each has its own Global Interpreter Lock (GIL)
58+
- Each has its own memory space
59+
60+
**Configuration:**
61+
```python
62+
workers = 4 # Creates 4 worker processes
63+
```
64+
65+
**Why separate processes:**
66+
- **True parallelism**: Multiple processes can run on multiple CPU cores
67+
- **Isolation**: If one worker crashes, others continue
68+
- **GIL bypass**: Each process has its own GIL, so they can truly run in parallel
69+
70+
**Example with 4 workers:**
71+
```
72+
Master Process
73+
├── Worker Process #1 (PID 1001) - Python interpreter #1
74+
├── Worker Process #2 (PID 1002) - Python interpreter #2
75+
├── Worker Process #3 (PID 1003) - Python interpreter #3
76+
└── Worker Process #4 (PID 1004) - Python interpreter #4
77+
```
78+
79+
### Level 3: Threads (within each worker)
80+
81+
**What they are:**
82+
- Lightweight execution units within a process
83+
- Share the same memory space as their parent worker
84+
- Share the same GIL (Global Interpreter Lock)
85+
86+
**Configuration:**
87+
```python
88+
threads = 2 # 2 threads per worker
89+
```
90+
91+
**Why threads:**
92+
- **I/O concurrency**: While one thread waits for DB/network, another can process
93+
- **Efficiency**: Threads are lighter than processes
94+
- **Limited by GIL**: In CPU-bound tasks, only one thread runs at a time
95+
96+
**Example within Worker #1:**
97+
```
98+
Worker Process #1
99+
├── Thread #1 (handles request A)
100+
└── Thread #2 (handles request B)
101+
# Both threads share Worker #1's memory and GIL
102+
```
103+
104+
**GIL Impact:**
105+
- Python's GIL allows only one thread to execute Python bytecode at a time
106+
- However, threads can still be useful for I/O-bound operations (DB queries, network calls)
107+
- While Thread #1 waits for database response, Thread #2 can handle another request
108+
109+
### Level 4: Connection Pools (per worker)
110+
111+
**What they are:**
112+
- Pool of reusable connections to external resources
113+
- Database connections (PostgreSQL)
114+
- Redis connections
115+
- HTTP client connections
116+
117+
**Configuration:**
118+
```python
119+
POOL_SIZE = 5 # 5 connections in pool
120+
MAX_OVERFLOW = 5 # 5 additional when pool exhausted
121+
```
122+
123+
**Why connection pools:**
124+
- Creating connections is expensive (network overhead, authentication)
125+
- Reusing connections is much faster
126+
- Limits number of connections to prevent resource exhaustion
127+
128+
**Example within Worker #1:**
129+
```
130+
Worker Process #1
131+
├── Thread #1
132+
│ └── Uses Connection Pool #1
133+
│ ├── DB Connection 1
134+
│ ├── DB Connection 2
135+
│ ├── DB Connection 3
136+
│ └── Redis Connection
137+
└── Thread #2
138+
└── Uses Connection Pool #1 (same pool, different connections)
139+
├── DB Connection 4
140+
├── DB Connection 5
141+
└── Redis Connection (shared)
142+
```
143+
144+
## Complete Example: 4 Workers × 2 Threads
145+
146+
```
147+
Gunicorn Master (Port 9092)
148+
149+
├── Worker 1 (Process PID 1001)
150+
│ ├── Thread 1 → Can handle 1 request
151+
│ │ └── Connection Pool 1
152+
│ │ ├── DB Conn 1-5
153+
│ │ └── Redis Conn
154+
│ └── Thread 2 → Can handle 1 request
155+
│ └── Connection Pool 1 (shared)
156+
│ ├── DB Conn 1-5 (reused)
157+
│ └── Redis Conn (shared)
158+
159+
├── Worker 2 (Process PID 1002)
160+
│ ├── Thread 1 → Can handle 1 request
161+
│ │ └── Connection Pool 2
162+
│ │ ├── DB Conn 1-5 (separate from Worker 1)
163+
│ │ └── Redis Conn
164+
│ └── Thread 2 → Can handle 1 request
165+
│ └── Connection Pool 2 (shared)
166+
167+
├── Worker 3 (Process PID 1003)
168+
│ ├── Thread 1 → Can handle 1 request
169+
│ └── Thread 2 → Can handle 1 request
170+
171+
└── Worker 4 (Process PID 1004)
172+
├── Thread 1 → Can handle 1 request
173+
└── Thread 2 → Can handle 1 request
174+
175+
Total Concurrent Requests: 4 workers × 2 threads = 8 requests simultaneously
176+
```
177+
178+
## Request Flow Example
179+
180+
```
181+
1. HTTP Request arrives at Port 9092
182+
183+
2. Gunicorn Master accepts connection
184+
185+
3. Master distributes to Worker 2 (round-robin or least busy)
186+
187+
4. Worker 2 assigns to Thread 1 (available thread)
188+
189+
5. Thread 1 processes request:
190+
- Gets DB connection from Connection Pool 2
191+
- Executes database query
192+
- Gets Redis connection from pool
193+
- Caches result
194+
- Returns response
195+
196+
6. Thread 1 releases connections back to pool
197+
198+
7. Thread 1 ready for next request
199+
```
200+
201+
## Connection Pool Hierarchy
202+
203+
### Database Connection Pool (per worker process)
204+
205+
```
206+
Worker Process #1
207+
└── SQLAlchemy Connection Pool
208+
├── Pool Size: 5 connections
209+
├── Max Overflow: 5 connections
210+
└── Total Max: 10 connections
211+
212+
Worker Process #2
213+
└── SQLAlchemy Connection Pool (separate)
214+
├── Pool Size: 5 connections
215+
├── Max Overflow: 5 connections
216+
└── Total Max: 10 connections
217+
218+
Total across all workers: 4 workers × 10 connections = 40 max DB connections
219+
```
220+
221+
**Important:**
222+
- Each worker has its own connection pool
223+
- Threads within a worker share the pool
224+
- Connections are reused across requests
225+
- When pool exhausted, new connections created (up to MAX_OVERFLOW)
226+
227+
### Redis Connection Pool (shared or per worker)
228+
229+
```
230+
Option 1: Per Worker (current setup)
231+
Worker 1 → Redis Connection Pool 1
232+
Worker 2 → Redis Connection Pool 2
233+
Worker 3 → Redis Connection Pool 3
234+
Worker 4 → Redis Connection Pool 4
235+
236+
Option 2: Shared (if using connection pooling library)
237+
All Workers → Single Redis Connection Pool (thread-safe)
238+
```
239+
240+
## Mathematical Relationship
241+
242+
### Capacity Calculation
243+
244+
```
245+
Concurrent Requests = Workers × Threads
246+
247+
Example:
248+
- Workers = 4
249+
- Threads = 2
250+
- Concurrent Requests = 4 × 2 = 8
251+
252+
Throughput Calculation:
253+
Throughput (req/sec) = Concurrent Requests × (1000ms / Average Response Time)
254+
255+
Example:
256+
- 8 concurrent requests
257+
- Average response time = 100ms
258+
- Throughput = 8 × (1000 / 100) = 80 req/sec
259+
260+
But with pipelining and request queuing:
261+
Actual throughput can be higher (1000-2000 req/sec)
262+
```
263+
264+
### Connection Pool Sizing
265+
266+
```
267+
Recommended Pool Size per Worker = (Workers × Threads) / Workers
268+
269+
For 4 workers, 2 threads:
270+
Pool Size = (4 × 2) / 4 = 2 connections per worker minimum
271+
272+
Better: Pool Size = Threads × 2-3
273+
Pool Size = 2 × 3 = 6 connections per worker
274+
275+
Total DB Connections = Workers × Pool Size
276+
Total DB Connections = 4 × 6 = 24 connections (reasonable)
277+
```
278+
279+
## Key Differences
280+
281+
| Aspect | Workers | Threads | Connections |
282+
|--------|---------|---------|-------------|
283+
| **Level** | Process-level | Thread-level | Resource-level |
284+
| **Isolation** | Separate memory | Shared memory | External resource |
285+
| **Parallelism** | True (multi-core) | Limited (GIL) | N/A |
286+
| **Communication** | IPC/message passing | Shared variables | Network protocol |
287+
| **Failure Impact** | Isolated (others continue) | Affects process | Recoverable |
288+
| **Memory** | Separate heap | Shared heap | External |
289+
| **Creation Cost** | High (fork) | Low (lightweight) | Medium (network) |
290+
291+
## Best Practices
292+
293+
### 1. Worker Count
294+
- **Formula**: (2 × CPU cores) + 1
295+
- **For I/O-bound**: More workers (4-8)
296+
- **For CPU-bound**: Fewer workers (2-4)
297+
- **Your setup**: 4 workers (good for mixed workload)
298+
299+
### 2. Thread Count
300+
- **For I/O-bound**: 2-4 threads per worker
301+
- **For CPU-bound**: 1 thread per worker (threads don't help with GIL)
302+
- **Your setup**: 2 threads (good for database/network I/O)
303+
304+
### 3. Connection Pool Size
305+
- **Per worker**: Threads × 2-3
306+
- **Example**: 2 threads × 3 = 6 connections per worker
307+
- **Total**: Workers × Pool Size
308+
- **Database limit**: Don't exceed database max_connections
309+
310+
## Real-World Example
311+
312+
**Your Current Configuration:**
313+
```
314+
Gunicorn Master
315+
├── Worker 1 (handles requests 1, 9, 17, ...)
316+
│ ├── Thread 1 (handles requests 1, 5, 9, ...)
317+
│ └── Thread 2 (handles requests 2, 6, 10, ...)
318+
├── Worker 2 (handles requests 2, 10, 18, ...)
319+
│ ├── Thread 1 (handles requests 3, 7, 11, ...)
320+
│ └── Thread 2 (handles requests 4, 8, 12, ...)
321+
├── Worker 3
322+
└── Worker 4
323+
324+
At any moment:
325+
- 8 requests can be processed simultaneously
326+
- Each worker has its own connection pool
327+
- Threads share connection pool within worker
328+
- Connections are reused across requests
329+
```
330+
331+
## Summary
332+
333+
**Hierarchy Order:**
334+
1. **Gunicorn Master** - Coordinates everything
335+
2. **Workers** (Processes) - Handle request processing (4 workers)
336+
3. **Threads** (within workers) - Enable concurrency (2 threads each)
337+
4. **Connection Pools** (per worker) - Reuse expensive connections
338+
339+
**Key Takeaway:**
340+
- **Workers** = True parallelism (different CPU cores)
341+
- **Threads** = I/O concurrency (waiting for DB/network)
342+
- **Connections** = Resource efficiency (reuse expensive connections)
343+
344+
**Your Setup:**
345+
- 4 workers × 2 threads = 8 concurrent requests
346+
- Each worker maintains its own DB/Redis connection pool
347+
- Can handle 1000-2000 req/sec with proper caching and optimization
348+

0 commit comments

Comments
 (0)