You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: Fix README to accurately reflect Gunicorn implementation
- Update Project Evolution to show 4 years ago (matching user's edit)
- Change 'Event-driven with async/await' to 'Gunicorn multi-process, multi-thread'
- Update architecture diagrams from 'Event Loop' to 'Gunicorn Workers'
- Fix performance metrics to reflect actual Gunicorn configuration
- Update comparison table with accurate Gunicorn details
- Remove duplicate 'Connection pooling' lesson
- Update timeline from 3-year to 4-year gap
- Clarify that async/await is not currently used
-4 workers × 2 threads = 8 concurrent requests per service
26
26
27
27
**Performance Improvements:**
28
28
- 10x throughput increase
@@ -123,11 +123,11 @@ Building this project taught me a lot about backend architecture. Here are the m
123
123
124
124
**Context Switching Cost**: I learned the hard way that more threads doesn't mean better performance. Context switching has real overhead (1-10 microseconds per switch), and cache invalidation can cause 60-100x slowdowns. Sometimes 50 threads actually outperform 200 threads.
125
125
126
-
**Python's GIL**: The Global Interpreter Lock blocks CPU parallelism (multiple threads can't run Python code simultaneously), but it doesn't block I/O parallelism since threads release the GIL during I/O operations. For I/O-bound workloads like this, an event loop with async/await works really well.
126
+
**Python's GIL**: The Global Interpreter Lock blocks CPU parallelism (multiple threads can't run Python code simultaneously), but it doesn't block I/O parallelism since threads release the GIL during I/O operations. For I/O-bound workloads, Gunicorn's multi-process, multi-thread architecture works well, and async/await would be even better for very high concurrency scenarios.
127
127
128
128
**Connection Pooling**: This was a game changer. Creating a new database connection takes 50-100ms (TCP handshake, SSL, authentication), but reusing one from a pool takes less than 1ms. That's a 100x performance improvement right there.
129
129
130
-
**Event Loop Architecture**: A single thread can handle 1000+ concurrent streams when you use non-blocking I/O. This prevents blocking on network or database operations, which is perfect for I/O-heavy workloads like event stream monitoring.
130
+
**Gunicorn Architecture**: Multiple worker processes with threads provide good concurrency while maintaining process isolation. Each worker can handle multiple requests concurrently, and connection pooling ensures efficient database access. This architecture works well for microservices with moderate to high request volumes.
131
131
132
132
**Note**: While async/await and event loops are powerful for I/O-bound workloads, EventStreamMonitor currently uses **Gunicorn with sync workers and threads** for simplicity and compatibility with existing Flask code. For a detailed comparison of concurrency models (Gunicorn vs Async/Await vs ThreadPoolExecutor), see [Concurrency Models Explained](docs/concurrency/CONCURRENCY_MODELS_EXPLAINED.md).
133
133
@@ -142,26 +142,27 @@ Avg Latency: 50-100ms
142
142
Throughput: ~2,000 events/sec
143
143
```
144
144
145
-
### After Refactoring (Event-Driven)
145
+
### After Refactoring (Gunicorn Multi-Process)
146
146
```
147
-
Concurrent Streams: 1000+
148
-
Memory Usage: ~100MB
147
+
Concurrent Requests: 8 per service (4 workers × 2 threads)
148
+
Memory Usage: ~100MB per worker
149
149
CPU Usage: 60% (actual work)
150
150
Avg Latency: <10ms
151
-
Throughput: ~20,000 events/sec
151
+
Throughput: ~2,000 requests/sec per service
152
152
```
153
153
154
154
## Architecture Comparison
155
155
156
-
| Aspect | Old (Thread-Per-Stream) | New (Event-Driven) |
| Threading Model | 200 OS threads |4 processes × 2 threads = 8 concurrent|
159
+
| Memory per Request|~2MB |~100KB |
160
+
| Context Switching | High (expensive) |Moderate (between threads)|
161
161
| Database Connections | New per query | Pooled (20 total) |
162
162
| Connection Overhead | 50-100ms | <1ms |
163
-
| Max Concurrent Streams|~200 |1000+|
163
+
| Max Concurrent Requests|~200 |8 per service (scales with instances)|
164
164
| CPU Efficiency | 30% (rest in switching) | 60% (actual work) |
165
+
| Process Isolation | No | Yes (one crash doesn't kill others) |
165
166
166
167
## Quick Start
167
168
@@ -348,7 +349,7 @@ My research notes on Redis's threading architecture. This goes into:
348
349
349
350
**Python's GIL isn't always a problem.** For I/O-bound workloads like event stream monitoring, the GIL has minimal impact because it's released during I/O operations. The whole "GIL is bad" narrative doesn't apply here.
350
351
351
-
**Async/await beats threading for I/O.**Event loops with async/await provide much better scalability for I/O-heavy workloads than traditional threading. The numbers don't lie.
352
+
**Gunicorn's multi-process architecture provides good balance.**Multiple worker processes with threads give us process isolation, good concurrency, and efficient resource usage without the complexity of async/await migration.
352
353
353
354
## Development
354
355
@@ -457,7 +458,7 @@ See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes and updates.
457
458
458
459
**Kamal Sai Devarapalli** - [GitHub](https://github.com/Ricky512227)
459
460
460
-
This project represents my journey from naive threading implementations to production-grade event-driven architecture. The 3-year gap between versions taught me more about backend engineering than any tutorial ever could.
461
+
This project represents my journey from naive threading implementations to production-grade microservices architecture. The 4-year gap between versions taught me more about backend engineering than any tutorial ever could.
0 commit comments