93 lines
2.1 KiB
Markdown
93 lines
2.1 KiB
Markdown
# Performance Bug Debug Example
|
|
|
|
Debugging slow database queries and N+1 problems.
|
|
|
|
## Symptom
|
|
|
|
API endpoint taking 4.5 seconds to respond (target: < 200ms).
|
|
|
|
## Profiling
|
|
|
|
```python
|
|
import cProfile
|
|
import pstats
|
|
|
|
profiler = cProfile.Profile()
|
|
profiler.enable()
|
|
|
|
response = await get_users_with_posts()
|
|
|
|
profiler.disable()
|
|
stats = pstats.Stats(profiler)
|
|
stats.sort_stats('cumulative')
|
|
stats.print_stats(10)
|
|
```
|
|
|
|
### Profile Output
|
|
|
|
```
|
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
|
100 4.321 0.043 4.321 0.043 database.py:42(execute_query)
|
|
1 0.089 0.089 4.410 4.410 users.py:15(get_users_with_posts)
|
|
```
|
|
|
|
**Issue**: Database query called 100 times! (N+1 problem)
|
|
|
|
## Code Analysis
|
|
|
|
```python
|
|
# BAD: N+1 Query Problem
|
|
async def get_users_with_posts():
|
|
users = await db.users.find_all() # 1 query
|
|
|
|
result = []
|
|
for user in users: # 100 iterations
|
|
posts = await db.posts.find({"user_id": user.id}) # N queries!
|
|
result.append({"user": user, "posts": posts})
|
|
|
|
return result # Total: 101 queries (1 + 100)
|
|
```
|
|
|
|
## Fix: Use Join/Eager Loading
|
|
|
|
```python
|
|
# GOOD: Single Query with Join
|
|
async def get_users_with_posts():
|
|
query = """
|
|
SELECT
|
|
users.*,
|
|
json_agg(posts.*) as posts
|
|
FROM users
|
|
LEFT JOIN posts ON posts.user_id = users.id
|
|
GROUP BY users.id
|
|
"""
|
|
result = await db.execute(query) # 1 query total!
|
|
return result
|
|
```
|
|
|
|
## Performance Comparison
|
|
|
|
| Approach | Queries | Time |
|
|
|----------|---------|------|
|
|
| **Before (N+1)** | 101 | 4.5s ❌ |
|
|
| **After (Join)** | 1 | 85ms ✅ |
|
|
|
|
**Improvement**: 53x faster!
|
|
|
|
## Prevention
|
|
|
|
1. **Query logging**: Log all database queries in development
|
|
2. **Performance tests**: Assert query count < threshold
|
|
3. **APM monitoring**: Track query patterns in production (Datadog, New Relic)
|
|
|
|
```python
|
|
# Performance test
|
|
def test_get_users_with_posts_query_count(query_counter):
|
|
get_users_with_posts()
|
|
assert query_counter.count <= 1, f"Expected 1 query, got {query_counter.count}"
|
|
```
|
|
|
|
---
|
|
|
|
**Result**: N+1 detected and fixed. Performance SLA met (< 200ms).
|