Decision: Python 3.14 Adoption¶
Date: 2026-01-21 Status: Implemented
Implementation Status (Updated 2026-01-28): Python 3.14 is now the primary runtime. The
pyproject.tomlspecifiesrequires-python = ">=3.14"and all tooling (ruff, mypy) is configured for Python 3.14. Phase 1 is complete.
Context¶
The Home Security Intelligence project requires high-performance processing for:
- Real-time AI inference: YOLO26 object detection on video streams
- Concurrent request handling: WebSocket connections from multiple cameras
- Batch processing: 90-second time windows with parallel analysis
- Background tasks: Risk scoring with Nemotron LLM
Python's Global Interpreter Lock (GIL) has historically limited CPU-bound parallel performance. With Python 3.14's experimental free-threading support (PEP 703), we can potentially achieve significant performance improvements for our multi-threaded workloads.
Current Limitations¶
| Workload | Current Approach | Limitation |
|---|---|---|
| Frame processing | ProcessPoolExecutor | IPC overhead, memory copies |
| WebSocket broadcast | ThreadPoolExecutor | GIL limits parallelism |
| Database queries | asyncio | Works well, no GIL impact |
| LLM inference | Single-threaded | GPU-bound, not CPU-limited |
Evaluation Criteria¶
- Performance improvement for CPU-bound parallel workloads
- Compatibility with existing codebase
- Library ecosystem support
- Production stability
- Migration effort
Decision Summary¶
Adopt Python 3.14 with optional free-threading support. The project will:
- Target Python 3.14 as the primary runtime
- Use the free-threaded build (3.14t) for production workloads
- Maintain backward compatibility with Python 3.12+ for development
- Adopt UUID7 for new ID generation
- Leverage Zstd compression where beneficial
Options Evaluated¶
Option 1: Stay on Python 3.12/3.13 (Status Quo)¶
| Pros | Cons |
|---|---|
| Stable, well-tested | No parallel performance gains |
| Full library compatibility | Continued use of ProcessPoolExecutor |
| No migration effort | Higher memory usage for parallelism |
Option 2: Python 3.14 (Standard Build)¶
| Pros | Cons |
|---|---|
| New features (UUID7, improved errors) | GIL still present |
| Better performance baseline | Limited parallel improvement |
| Library compatibility improving | Some packages may lag |
Option 3: Python 3.14t (Free-Threaded Build)¶
| Pros | Cons |
|---|---|
| True parallel thread execution | Experimental feature |
| Significant CPU-bound speedups | Some C extensions may break |
| Reduced memory from shared objects | Requires thread-safe code review |
| Native UUID7 and Zstd | Potential for new concurrency bugs |
Option 4: Alternative Languages (Rust/Go for hot paths)¶
| Pros | Cons |
|---|---|
| Maximum performance | Significant rewrite effort |
| No GIL concerns | Loss of Python ecosystem |
| Type safety | Team skill requirements |
Rationale¶
We selected Option 3 (Python 3.14t) with graceful degradation to Option 2 for the following reasons:
1. Performance Benchmarks¶
Our benchmark suite shows significant improvements:
| Benchmark | Python 3.12 | Python 3.14t | Speedup |
|---|---|---|---|
| CPU parallel (4 threads) | 245ms | 65ms | 3.8x |
| CPU parallel (8 threads) | 245ms | 35ms | 7.0x |
| Frame preprocessing (batch) | 1200ms | 320ms | 3.75x |
2. Architecture Alignment¶
Our existing architecture already uses ThreadPoolExecutor extensively:
# Current pattern (limited by GIL)
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(process_frame, frames))
With free-threading, this pattern immediately benefits without code changes.
3. Gradual Adoption Path¶
We can adopt incrementally:
- Phase 1: Development on 3.14, production on 3.12
- Phase 2: Production testing on 3.14 (standard)
- Phase 3: Production deployment on 3.14t
- Phase 4: Optimize for free-threading patterns
4. Feature Benefits Beyond Performance¶
| Feature | Benefit |
|---|---|
| UUID7 | Better database indexing, sortable IDs |
| Zstd compression | Faster caching, lower storage |
| Improved errors | Faster debugging |
| JIT compilation (planned) | Future performance gains |
5. Risk Mitigation¶
- Feature detection patterns ensure backward compatibility
- Comprehensive benchmark suite validates performance claims
- Gradual rollout allows reverting if issues arise
Consequences¶
Positive¶
- 3-7x speedup for CPU-bound parallel workloads
- Simpler code: Use ThreadPoolExecutor instead of ProcessPoolExecutor
- Lower memory: Shared objects between threads
- Better IDs: UUID7 improves database performance
- Future-proof: Positioned for Python's parallel evolution
Negative¶
- Library compatibility: Some C extensions need updates
- Thread safety: Need to audit shared mutable state
- Debugging complexity: New class of concurrency bugs possible
- Learning curve: Team needs to understand GIL removal implications
Neutral¶
- Docker images: Need to build with 3.14t base
- CI/CD updates: Test matrix needs 3.14t variant
- Monitoring: Need to add thread contention metrics
Implementation Plan¶
Phase 1: Foundation (Week 1)¶
- [x] Create benchmark suite (
scripts/benchmark_py314.py) - [x] Document features (
docs/development/python-3.14-features.md) - [x] Create this ADR
- [x] Update
pyproject.tomlfor Python 3.14 compatibility
Phase 2: Compatibility (Week 2)¶
- [ ] Audit ThreadPoolExecutor usage for thread safety
- [ ] Update Dockerfile for 3.14t base image
- [ ] Test all dependencies with 3.14t
- [ ] Update CI to include 3.14t test matrix
Phase 3: Optimization (Week 3-4)¶
- [ ] Identify hotspots benefiting from free-threading
- [ ] Refactor ProcessPoolExecutor to ThreadPoolExecutor where beneficial
- [ ] Add thread contention monitoring
- [ ] Performance regression testing
Phase 4: Production (Week 5+)¶
- [ ] Staged rollout to production
- [ ] Monitor performance metrics
- [ ] Document lessons learned
Feature Detection Patterns¶
Runtime Detection¶
import sys
def supports_free_threading() -> bool:
"""Check if running in free-threaded mode."""
if hasattr(sys, '_is_gil_enabled'):
return not sys._is_gil_enabled()
return False
def supports_uuid7() -> bool:
"""Check if UUID7 is available."""
import uuid
return hasattr(uuid, 'uuid7')
def supports_zstd() -> bool:
"""Check if Zstd compression is available."""
try:
import compression.zstd
return True
except ImportError:
return False
Graceful Degradation¶
# UUID generation with fallback
import uuid
import sys
def generate_id() -> uuid.UUID:
if sys.version_info >= (3, 14) and hasattr(uuid, 'uuid7'):
return uuid.uuid7()
return uuid.uuid4()
# Parallel execution with fallback
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def get_cpu_executor(max_workers: int):
if supports_free_threading():
return ThreadPoolExecutor(max_workers=max_workers)
return ProcessPoolExecutor(max_workers=max_workers)
Monitoring Requirements¶
Metrics to Track¶
- Thread contention rate: Lock wait times
- Memory usage: Per-worker memory consumption
- CPU utilization: Should increase with free-threading
- Request latency: P50, P95, P99 for API endpoints
- Throughput: Frames processed per second
Alerting Thresholds¶
| Metric | Warning | Critical |
|---|---|---|
| Thread contention | > 10ms avg | > 50ms avg |
| Memory per worker | > 500MB | > 1GB |
| CPU utilization | < 60% (underutilized) | > 95% |
Rollback Plan¶
If issues arise with Python 3.14t:
- Immediate: Switch Dockerfile to 3.14 (standard build)
- Short-term: Revert to Python 3.12/3.13
- Long-term: Re-evaluate when 3.14t stabilizes
Related Documentation¶
- Python 3.14 Features Guide
- Benchmark Script
- PEP 703 - Making the Global Interpreter Lock Optional
- PEP 778 - Add UUID version 7
Decision Approval¶
| Role | Name | Date | Approval |
|---|---|---|---|
| Tech Lead | - | 2026-01-21 | Pending |
| Backend Lead | - | - | Pending |
| DevOps Lead | - | - | Pending |