OpenRailAssociation / osrd

An open source web application for railway infrastructure design, capacity analysis, timetabling and simulation
https://osrd.fr
GNU Lesser General Public License v3.0
437 stars 40 forks source link

[meta] Improve editoast railjson performance #4657

Open Tristramg opened 1 year ago

Tristramg commented 1 year ago

https://github.com/orgs/osrd-project/projects/12/views/7?pane=issue&itemId=25095490

On a big railjson, the import lasts 3:03 minutes. 1:17 is spent on importing 0:41 is spent on generating

There seems to be a low hanging fruit on: https://github.com/osrd-project/osrd/blob/dev/editoast/src/models/infra.rs#L115-L138 where all the requests are made sequentially. There is a single postgres process at 100% during the import.

If we want to parallelize the import, we need async sql (using something else than diesel?) and we might need to split the transaction (and writing rollback code by hand — but a delete cascade should be quite trivial).

Should we accept to have two SQL connectors (diesel and sqlx ?) ? Or try to switch the whole codebase to https://github.com/weiznich/diesel_async (is would require to also change the connection pool)

Tristramg commented 1 year ago

Première étape pour estimer les performances de l’asynchrone sur pg, uniquement sur l’import du railjson dans la base (pas la génération).

Le code à l’arrache est visible ici https://github.com/osrd-project/osrd/commit/9d0b52a628d015a392e8698ccb297bb391f4d76c

Les fonctions sont dupliquées pour pouvoir les tester indépendamment. Il y a trois cas testés:

Les 3 cas ont été testés avec le jeu de données de la France avec hyperfine

hyperfine --prepare 'cargo build --release' --parameter-scan async 0 2 -D 1 'cargo run --release -- --psql-port=15432 --run-async=
{async} import-railjson france ./france.railjson
'                                                                                                                                                     
# Asynchrone séquentiel
Benchmark 1: cargo run --release -- --psql-port=15432 --run-async=0 import-railjson france ./france.railjson                                                                                          
  Time (mean ± σ):     24.028 s ±  1.256 s    [User: 4.678 s, System: 0.618 s]                                                                                                                        
  Range (min … max):   23.079 s … 27.229 s    10 runs                                                                                                                                                 

# Sans transaction                                                                                                                                                                                                      
Benchmark 2: cargo run --release -- --psql-port=15432 --run-async=1 import-railjson france ./france.railjson                                                                                          
  Time (mean ± σ):     15.343 s ±  3.783 s    [User: 4.649 s, System: 0.601 s]                                                                                                                        
  Range (min … max):   11.270 s … 21.668 s    10 runs                                                                                                                                                 

# Séquentiel synchrone
Benchmark 3: cargo run --release -- --psql-port=15432 --run-async=2 import-railjson france ./france.railjson                                                                                          
  Time (mean ± σ):     29.963 s ±  3.912 s    [User: 4.607 s, System: 0.432 s]                                                                                                                        
  Range (min … max):   25.423 s … 37.007 s    10 runs                                                

Il y a donc un léger gain à faire de l’asynchrone tout en restant séquentiel.

En enlevant totalement les transactions, le gain est significatif car on peut utiliser des connexions différents. Par contre, en cas de soucis, bien évidemment pas de rollback automatique.

Il semble que PG soit capable de paralléliser automatiquement avec la même connexion (permettant de garder la transaction) https://docs.rs/diesel-async/0.3.2/diesel_async/struct.AsyncPgConnection.html# mais on n’en voit pas les bénéfices ici.

Je pense que c’est lié au fait de découper la requête en plusieurs chunks ( https://github.com/osrd-project/osrd/commit/9d0b52a628d015a392e8698ccb297bb391f4d76c#diff-2dd65b6c4e3d30284b3a9058d429ad38edabbcaf448f8e52372b8c2e917c369fR88 ) Il faut encore explorer le comportement si persist_batch_async retournait un Vec pour faire un grand try_join_all