Co-Study4Grid / docs /performance /history /shared-network.md
github-actions[bot]
Deploy f7b8bf6
1c730d1
|
Raw
History Blame Contribute Delete
4.8 kB
# Mutualisation du pypowsybl Network entre `network_service` et `recommender_service`
## Observation (v8 trace)
Le NAD prefetch introduit en v8 (`docs/performance/history/nad-prefetch.md`) a délivré
`/api/network-diagram` en **516 ms** au lieu de 6 385 ms, mais
`/api/config` est passé de 14 790 ms à **19 171 ms (+4.4 s)**. Le gain
net a été de seulement ~1.5 s sur la critical path.
En auditant le code, on s'aperçoit qu'on charge le **même `.xiidm`
jusqu'à trois fois** pendant `/api/config` :
1. `network_service.load_network()``pn.load(file_path)`, ~3-5 s
2. `recommender_service._get_base_network()``pp.network.load(str(network_file))`, ~3-5 s **doublon complet**
3. `setup_environment_configs_pypowsybl()` dans grid2op — backend pypowsybl charge encore une fois, ~3-5 s
Les appels #1 et #2 chargent **exactement le même fichier** et produisent
**la même Network pypowsybl**. La pré-chauffe `_base_network` que fait
`prefetch_base_nad_async()` en main thread est responsable d'une grosse
partie du +4.4 s sur `/api/config`.
## Change
`_get_base_network()` préfère maintenant **réutiliser la Network déjà
chargée par `network_service`** plutôt que de la re-parser :
```python
def _get_base_network(self):
if self._base_network is not None:
return self._base_network
# Préfère l'instance déjà chargée par network_service.
from expert_backend.services.network_service import network_service
if network_service.network is not None:
n = network_service.network
n.get_line_ids = lambda: n.get_lines().index.tolist()
self._base_network = n
self._capture_initial_pst_taps(n)
return self._base_network
# Fallback : pp.network.load(config.ENV_PATH) pour les tests /
# appels qui bypass /api/config.
```
## Sûreté de partage
Les deux services tiennent une référence vers le **même objet
pypowsybl Network**. Les accès restent sûrs :
- **`network_service`** ne fait que des lectures (`get_lines`,
`get_voltage_levels`, …), jamais de variant switch.
- **`recommender_service`** switche les variants dans `_get_n_variant`
/ `_get_n1_variant` mais restaure toujours le variant d'origine dans
un `try/finally`, donc toute lecture depuis `network_service` voit
un état cohérent.
- Le monkey-patch `get_line_ids` est idempotent et purement additif.
- `_capture_initial_pst_taps` est spécifique au recommender (state
isolé) et doit toujours s'exécuter pour capturer les tap positions
N-state.
## Invariants (testés)
`test_recommender_service.py::TestGetBaseNetworkMutualisation` :
- `test_reuses_network_service_network_when_available` — quand
`network_service.network` est peuplé, `_get_base_network()` ne
déclenche AUCUN nouveau `pp.network.load()` et retourne exactement
la même instance.
- `test_falls_back_to_standalone_load_when_network_service_empty` —
quand `network_service.network is None` (tests, appelants qui
bypass `/api/config`), on retombe sur le chargement standalone.
Le test existant `test_direct_file_loading.py::test_get_base_network_with_file_path`
est mis à jour pour forcer explicitement le chemin fallback en
mettant `network_service.network = None`.
## Impact attendu
Baseline v8 (avec NAD prefetch seul) :
| Segment | v8 mesuré |
|---|---|
| `/api/config` | 19 171 ms |
| `/api/network-diagram` | 516 ms |
| Total critical path | ~20.5 s |
Attendu v9 (avec mutualisation) :
| Segment | v9 attendu |
|---|---|
| `/api/config` | ~14-16 s (−3-5 s : plus de pp.network.load doublon) |
| `/api/network-diagram` | ~500 ms (inchangé, cache toujours chaud) |
| Total critical path | **~15 s (−5 s vs v8, −6 s vs v7)** |
On récupère ainsi **une grosse partie du gain théorique du prefetch
NAD** qui avait été absorbé par le chargement dupliqué.
## Ce qui n'est PAS changé
- **Le chargement pypowsybl par grid2op** (#3 ci-dessus) reste en
place — il est enfoui dans `expert_op4grid_recommender` et ne peut
pas être court-circuité sans modifier la lib externe.
- **Le comportement des endpoints** est identique : seul le plomberie
interne change.
- **Les appelants qui construisent `RecommenderService` directement**
(tests, scripts d'intégration) continuent de fonctionner grâce au
fallback.
## Fichiers modifiés
| Fichier | Changement |
|---|---|
| `expert_backend/services/recommender_service.py` | `_get_base_network` préfère `network_service.network` quand disponible ; fallback `pp.network.load` préservé. |
| `expert_backend/tests/test_recommender_service.py` | Nouvelle classe `TestGetBaseNetworkMutualisation` (2 tests). |
| `expert_backend/tests/test_direct_file_loading.py` | `test_get_base_network_with_file_path` force explicitement le chemin fallback. |