Skip to content

Commit 659578c

Browse files
committed
docs: Add PERFORMANCE.md; update performance tests
1 parent 339d30b commit 659578c

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

PERFORMANCE.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Mailgun Python SDK: Performance & Architecture
2+
3+
This document outlines the architectural decisions made to ensure the Mailgun Python SDK remains blazingly fast, memory-efficient, and enterprise-ready.
4+
5+
If you are contributing to this repository, please review these principles before modifying core routing, transport, or instantiation logic.
6+
7+
## Core Optimizations
8+
9+
### 1. High-Concurrency Transport Layer (`httpx` & Context Management)
10+
11+
We replaced the legacy `requests` library with `httpx` to modernize network I/O and enforce strict connection pooling.
12+
13+
- **Optimized Connection Pooling:** The sync client is explicitly configured with `pool_maxsize=100`. This eliminates socket queuing bottlenecks under heavy multithreaded workloads, ensuring flat, predictable latency.
14+
- **Context Manager Enforcement:** The `Client` now implements `__enter__` and `__exit__`. By enforcing `with Client(...) as client:`, we guarantee the `httpx` connection pool is cleanly closed, eliminating socket leaks in long-running production services.
15+
- **Native AsyncIO:** The new `AsyncClient` allows for true non-blocking asynchronous throughput, enabling users to fire thousands of concurrent API requests without thread context-switching overhead.
16+
17+
### 2. Pre-Compiled Static Routing (`routes.py`)
18+
19+
String manipulation and regex compilation are historically slow operations in Python.
20+
21+
- **State Machine Pre-Warming:** We introduced a new, standalone `routes.py` module. All API path resolution patterns (`DOMAIN_REGEX`, `VERSION_REGEX`) are defined here as pre-compiled `re.Pattern` objects.
22+
- **Zero Per-Request Overhead:** By extracting regex compilation to the module level, the `build_url` execution path only evaluates pre-warmed C-level state machines. This completely eliminates regex parsing overhead during high-volume API requests.
23+
24+
### 3. The Zero-I/O Literal Lazy Router (`client.py`)
25+
26+
Traditional Python packages suffer a "Cold Boot" penalty when importing the main client triggers a cascade of sub-module file reads.
27+
28+
- **Zero Startup I/O:** Handler modules are imported strictly *inside* the `_load_handler` function body. They are not compiled from disk until the exact moment an API route is requested.
29+
- **O(1) Execution:** Using `@lru_cache` and static `if` branching, the routing cost is paid exactly once. Subsequent calls resolve instantly.
30+
- **SAST Compliance:** Explicit `from ... import ...` statements prove to static analysis tools that Arbitrary Code Execution is impossible.
31+
32+
### 4. Slot-Based Memory Allocation (`__slots__`)
33+
34+
All core classes (`Client`, `Endpoint`, `AsyncEndpoint`) strictly define `__slots__`.
35+
36+
- **Memory Density:** Removing the dynamic `__dict__` drastically reduces the RAM footprint of each instantiated client.
37+
- **Thread Stability:** `__slots__` enforces strict attribute management, preventing dynamic attribute mutation under heavy asynchronous or threaded workloads.
38+
39+
______________________________________________________________________
40+
41+
## Benchmarks (v1.6.0 vs. Current)
42+
43+
Our internal `pytest-benchmark` and `cProfile` suites verify these architectural gains.
44+
45+
| Metric | v1.6.0 (Baseline) | Optimized Architecture | Delta |
46+
| :------------------------------- | :---------------- | :--------------------- | :------------------ |
47+
| **Routing Speed (CPU Time)** | ~12,182 ns | **~833 ns** | **14.5x Faster** |
48+
| **Sync Pool Stability (StdDev)** | ~2.10 ms | **~0.22 ms** | **10x More Stable** |
49+
| **Cold-Boot Startup Time** | ~0.285s | **~0.175s** | **~40% Faster** |
50+
51+
*Note: The 14.5x routing speed increase is driven directly by the new `routes.py` module. The 10x stability increase is attributed to the new `httpx` connection pool. The 40% startup speed increase is attributed to the Literal Lazy Router eliminating `_io.BufferedReader` calls.*
52+
53+
______________________________________________________________________
54+
55+
## Profiling the Codebase
56+
57+
If you are modifying core internal logic, you must verify that you have not introduced I/O regressions or memory leaks.
58+
59+
**To profile Cold-Boot initialization:**
60+
61+
```bash
62+
python tests/test_boot.py
63+
```
64+
65+
**To benchmark the routing and throughput performance**
66+
67+
```bash
68+
pytest tests/test_perf.py --benchmark-compare
69+
```

tests/test_boot.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
import mailgun.client
1+
# tests/test_boot.py
2+
import cProfile
3+
import pstats
24

35
def boot_test() -> None:
4-
# This triggers the Config initialization and the __slots__ setup
6+
# Placing the import INSIDE the profiled function ensures we capture
7+
# the exact cost of Python crawling the disk to compile the modules.
8+
import mailgun.client
59
client = mailgun.client.Client(auth=("api", "key"))
610

711
if __name__ == "__main__":
12+
profiler = cProfile.Profile()
13+
14+
profiler.enable()
815
boot_test()
16+
profiler.disable()
17+
18+
# Sort by 'tottime' (Total internal time) and print the top 20 offenders
19+
stats = pstats.Stats(profiler).sort_stats('tottime')
20+
21+
print("\n--- TOP 20 TIME-CONSUMING OPERATIONS ---")
22+
stats.print_stats(20)

0 commit comments

Comments
 (0)