move
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Preventing Implicit ORM I/O (Asyncio)
|
||||
|
||||
Source:
|
||||
- https://docs.sqlalchemy.org/en/21/orm/extensions/asyncio.html#preventing-implicit-io-when-using-asyncsession
|
||||
- https://docs.sqlalchemy.org/en/21/orm/queryguide/relationships.html
|
||||
|
||||
Status: adopted
|
||||
Decision level: advisory
|
||||
Applies to: api-runtime, workers, tests
|
||||
Last reviewed: 2026-06-17
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Minimize unexpected database round-trips caused by attribute access in async ORM code.
|
||||
|
||||
In asyncio applications, hidden lazy loads are easy to miss and can produce runtime surprises. This guide defines explicit-loading defaults and progressive enforcement practices.
|
||||
|
||||
---
|
||||
|
||||
## Scope and Non-Goals
|
||||
|
||||
- In scope: relationship loading strategy, post-commit attribute access, explicit refresh/awaitable access patterns.
|
||||
- Out of scope: full ORM performance tuning and domain-specific query architecture.
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
- Prefer explicit eager loading for data required by endpoint/service outputs.
|
||||
- Avoid relying on implicit lazy-load behavior in request critical paths.
|
||||
- Keep `expire_on_commit=False` unless strict expiration behavior is intentionally required.
|
||||
- Use explicit refresh or awaitable-attribute access when loading deferred state is necessary.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Patterns
|
||||
|
||||
### Pattern A: Eager-load what you need
|
||||
|
||||
```python
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
stmt = select(User).options(selectinload(User.roles))
|
||||
users = (await session.scalars(stmt)).all()
|
||||
```
|
||||
|
||||
### Pattern B: Explicit refresh of named attributes
|
||||
|
||||
```python
|
||||
user = await session.get(User, user_id)
|
||||
await session.refresh(user, ["roles"])
|
||||
```
|
||||
|
||||
### Pattern C: Awaitable attribute access where needed
|
||||
|
||||
```python
|
||||
# Requires AsyncAttrs mixin on mapped base or class.
|
||||
roles = await user.awaitable_attrs.roles
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Practical Enforcement Model
|
||||
|
||||
Use phased enforcement:
|
||||
|
||||
1. High-traffic and latency-sensitive routes: enforce explicit eager loading.
|
||||
2. Background tasks and less critical paths: track and progressively tighten.
|
||||
3. Add review checks to prevent newly introduced implicit-load hotspots.
|
||||
|
||||
This keeps modernization pragmatic while reducing hidden I/O over time.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- Returning ORM objects from handlers and triggering lazy loads during serialization.
|
||||
- Assuming post-commit attribute access will always be loaded without explicit strategy.
|
||||
- Relying on broad expiration + implicit reload behavior in async request flows.
|
||||
- Enabling relationship patterns that hide SQL behavior in critical code paths.
|
||||
|
||||
---
|
||||
|
||||
## Operational Checks
|
||||
|
||||
- Endpoint query blocks define loader options for returned related data.
|
||||
- Critical handlers do not depend on incidental lazy loads.
|
||||
- Known exceptions are documented with rationale and follow-up items.
|
||||
|
||||
---
|
||||
|
||||
## Testing Checks
|
||||
|
||||
- Integration tests cover endpoints that return related objects.
|
||||
- Tests verify expected data is present without hidden secondary query surprises.
|
||||
- Regression tests exist for routes previously affected by implicit-load failures.
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- Start advisory: target high-risk paths first.
|
||||
- As coverage improves, elevate selected rules to mandatory in code review policy.
|
||||
Reference in New Issue
Block a user