Skip to content

Commit 95eb29e

Browse files
younesschrififlomonster
authored andcommitted
editoast: optimise unicity pathfinding and simulation
Signed-off-by: Youness CHRIFI ALAOUI <[email protected]>
1 parent d23f3c7 commit 95eb29e

File tree

3 files changed

+96
-53
lines changed

3 files changed

+96
-53
lines changed

editoast/src/core/pathfinding.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub enum PathfindingInputError {
137137
}
138138

139139
// Enum for not-found results and incompatible constraints
140-
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
140+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema, Default)]
141141
#[serde(tag = "error_type", rename_all = "snake_case")]
142142
pub enum PathfindingNotFound {
143143
NotFoundInBlocks {
@@ -148,6 +148,7 @@ pub enum PathfindingNotFound {
148148
track_section_ranges: Vec<TrackRange>,
149149
length: u64,
150150
},
151+
#[default]
151152
NotFoundInTracks,
152153
IncompatibleConstraints {
153154
relaxed_constraints_path: Box<PathfindingResultSuccess>,

editoast/src/views/path/pathfinding.rs

+55-30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use axum::extract::Json;
88
use axum::extract::Path;
99
use axum::extract::State;
1010
use axum::Extension;
11+
use derivative::Derivative;
1112
use editoast_authz::BuiltinRole;
1213
use editoast_common::units;
1314
use editoast_schemas::rolling_stock::LoadingGaugeType;
@@ -144,12 +145,16 @@ impl From<PathfindingCoreResult> for PathfindingResult {
144145
}
145146
}
146147

147-
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
148+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema, Derivative)]
149+
#[derivative(Default)]
148150
#[serde(tag = "failed_status", rename_all = "snake_case")]
149151
pub enum PathfindingFailure {
150152
PathfindingInputError(PathfindingInputError),
153+
#[derivative(Default)]
151154
PathfindingNotFound(PathfindingNotFound),
152-
InternalError { core_error: InternalError },
155+
InternalError {
156+
core_error: InternalError,
157+
},
153158
}
154159

155160
/// Compute a pathfinding
@@ -215,18 +220,37 @@ async fn pathfinding_blocks_batch(
215220
infra: &Infra,
216221
pathfinding_inputs: &[PathfindingInput],
217222
) -> Result<Vec<PathfindingResult>> {
223+
let mut hash_to_path_indexes: HashMap<String, Vec<usize>> = HashMap::default();
224+
let mut path_request_map: HashMap<String, PathfindingInput> = HashMap::default();
225+
let mut pathfinding_results =
226+
vec![PathfindingResult::Failure(PathfindingFailure::default()); pathfinding_inputs.len()];
227+
for (index, path_input) in pathfinding_inputs.iter().enumerate() {
228+
let pathfinding_hash = path_input_hash(infra.id, &infra.version, path_input);
229+
hash_to_path_indexes
230+
.entry(pathfinding_hash.clone())
231+
.or_default()
232+
.push(index);
233+
path_request_map
234+
.entry(pathfinding_hash.clone())
235+
.or_insert(path_input.clone());
236+
}
237+
238+
info!(
239+
nb_pathfindings = pathfinding_inputs.len(),
240+
nb_unique_pathfindings = hash_to_path_indexes.len()
241+
);
242+
218243
// Compute hashes of all path_inputs
219-
let hashes: Vec<_> = pathfinding_inputs
220-
.iter()
221-
.map(|input| path_input_hash(infra.id, &infra.version, input))
222-
.collect();
244+
let hashes = hash_to_path_indexes.keys().collect::<Vec<_>>();
223245

224246
// Try to retrieve the result from Valkey
225-
let mut pathfinding_results: Vec<Option<PathfindingResult>> =
247+
let pathfinding_cached_results: Vec<Option<PathfindingResult>> =
226248
valkey_conn.json_get_bulk(&hashes).await?;
249+
let pathfinding_cached_results: HashMap<_, _> =
250+
hashes.into_iter().zip(pathfinding_cached_results).collect();
227251

228252
// Report number of hit cache
229-
let nb_hit = pathfinding_results.iter().flatten().count();
253+
let nb_hit = pathfinding_cached_results.values().flatten().count();
230254
info!(
231255
nb_hit,
232256
nb_miss = pathfinding_inputs.len() - nb_hit,
@@ -235,11 +259,10 @@ async fn pathfinding_blocks_batch(
235259

236260
// Handle miss cache:
237261
debug!("Extracting locations from path items");
238-
let path_items: Vec<_> = pathfinding_results
262+
let path_items: Vec<_> = pathfinding_cached_results
239263
.iter()
240-
.zip(pathfinding_inputs)
241-
.filter(|(res, _)| res.is_none())
242-
.flat_map(|(_, input)| &input.path_items)
264+
.filter(|(_, res)| res.is_none())
265+
.flat_map(|(hash, _)| &path_request_map[*hash].path_items)
243266
.collect();
244267
let path_item_cache = PathItemCache::load(conn, infra.id, &path_items).await?;
245268

@@ -249,24 +272,25 @@ async fn pathfinding_blocks_batch(
249272
);
250273
let mut to_cache = vec![];
251274
let mut pathfinding_requests = vec![];
252-
let mut pathfinding_requests_index = vec![];
253-
for (index, (pathfinding_result, pathfinding_input)) in pathfinding_results
254-
.iter_mut()
255-
.zip(pathfinding_inputs)
256-
.enumerate()
257-
{
258-
if pathfinding_result.is_some() {
275+
let mut to_compute_hashes = vec![];
276+
for (hash, pathfinding_result) in pathfinding_cached_results.into_iter() {
277+
if let Some(result) = pathfinding_result {
278+
hash_to_path_indexes[hash]
279+
.iter()
280+
.for_each(|index| pathfinding_results[*index] = result.clone());
259281
continue;
260282
}
261-
283+
let pathfinding_input = &path_request_map[hash];
262284
match build_pathfinding_request(pathfinding_input, infra, &path_item_cache) {
263285
Ok(pathfinding_request) => {
264286
pathfinding_requests.push(pathfinding_request);
265-
pathfinding_requests_index.push(index);
287+
to_compute_hashes.push(hash);
266288
}
267289
Err(result) => {
268-
*pathfinding_result = Some(result.clone());
269-
to_cache.push((&hashes[index], result));
290+
hash_to_path_indexes[hash]
291+
.iter()
292+
.for_each(|index| pathfinding_results[*index] = result.clone());
293+
to_cache.push((hash, result));
270294
}
271295
}
272296
}
@@ -284,25 +308,26 @@ async fn pathfinding_blocks_batch(
284308
.into_iter()
285309
.collect();
286310

287-
for (index, path_result) in computed_paths.into_iter().enumerate() {
288-
let path_index = pathfinding_requests_index[index];
289-
let path = match path_result {
311+
for (path_result, hash) in computed_paths.into_iter().zip(to_compute_hashes) {
312+
let result = match path_result {
290313
Ok(path) => {
291-
to_cache.push((&hashes[path_index], path.clone().into()));
314+
to_cache.push((hash, path.clone().into()));
292315
path.into()
293316
}
294317
// TODO: only make HTTP status code errors non-fatal
295318
Err(core_error) => {
296319
PathfindingResult::Failure(PathfindingFailure::InternalError { core_error })
297320
}
298321
};
299-
pathfinding_results[path_index] = Some(path);
322+
hash_to_path_indexes[hash]
323+
.iter()
324+
.for_each(|index| pathfinding_results[*index] = result.clone());
300325
}
301326

302-
debug!(nb_results = to_cache.len(), "Caching pathfinding response");
327+
debug!(nb_cached = to_cache.len(), "Caching pathfinding response");
303328
valkey_conn.json_set_bulk(&to_cache).await?;
304329

305-
Ok(pathfinding_results.into_iter().flatten().collect())
330+
Ok(pathfinding_results)
306331
}
307332

308333
fn build_pathfinding_request(

editoast/src/views/train_schedule.rs

+39-22
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,8 @@ pub async fn consist_train_simulation_batch(
457457
.collect();
458458

459459
let mut simulation_results = vec![SimulationResponse::default(); train_schedules.len()];
460-
let mut to_sim = Vec::with_capacity(train_schedules.len());
460+
let mut to_sim: HashMap<String, Vec<usize>> = HashMap::default();
461+
let mut sim_request_map: HashMap<String, SimulationRequest> = HashMap::default();
461462
for (index, (pathfinding, train_schedule)) in
462463
pathfinding_results.iter().zip(train_schedules).enumerate()
463464
{
@@ -500,49 +501,65 @@ pub async fn consist_train_simulation_batch(
500501
// Compute unique hash of the simulation input
501502
let simulation_hash =
502503
train_simulation_input_hash(infra.id, &infra.version, &simulation_request);
503-
to_sim.push((index, simulation_hash, simulation_request));
504+
to_sim
505+
.entry(simulation_hash.clone())
506+
.or_default()
507+
.push(index);
508+
sim_request_map
509+
.entry(simulation_hash)
510+
.or_insert(simulation_request);
504511
}
505-
506-
let cached_results: Vec<Option<SimulationResponse>> = valkey_conn
507-
.json_get_bulk(&to_sim.iter().map(|(_, hash, _)| hash).collect::<Vec<_>>())
508-
.await?;
512+
info!(
513+
nb_train_schedules = train_schedules.len(),
514+
nb_unique_sim = to_sim.len()
515+
);
516+
let cached_simulation_hash = to_sim.keys().collect::<Vec<_>>();
517+
let cached_results: Vec<Option<SimulationResponse>> =
518+
valkey_conn.json_get_bulk(&cached_simulation_hash).await?;
509519

510520
let nb_hit = cached_results.iter().flatten().count();
511521
let nb_miss = to_sim.len() - nb_hit;
512522
info!(nb_hit, nb_miss, "Hit cache");
513523

514524
// Compute simulation from core
515525
let mut futures = Vec::with_capacity(nb_miss);
516-
let mut futures_index_hash = Vec::with_capacity(nb_miss);
517-
for ((train_index, train_hash, sim_request), sim_cached) in to_sim.iter().zip(cached_results) {
526+
let mut futures_hash = Vec::with_capacity(nb_miss);
527+
for (train_hash, sim_cached) in cached_simulation_hash.iter().zip(cached_results) {
518528
if let Some(sim_cached) = sim_cached {
519-
simulation_results[*train_index] = sim_cached;
529+
let train_indexes = &to_sim[*train_hash];
530+
for train_index in train_indexes {
531+
simulation_results[*train_index] = sim_cached.clone();
532+
}
520533
continue;
521534
}
535+
let sim_request = &sim_request_map[*train_hash];
522536
futures.push(Box::pin(sim_request.fetch(core.as_ref())));
523-
futures_index_hash.push((*train_index, train_hash));
537+
futures_hash.push(train_hash);
524538
}
525539

526540
let simulated: Vec<_> = futures::future::join_all(futures)
527541
.await
528542
.into_iter()
529543
.collect();
530544

531-
let mut is_cacheable = vec![false; train_schedules.len()];
532-
for (&(train_index, _), sim_res) in futures_index_hash.iter().zip(simulated) {
533-
(simulation_results[train_index], is_cacheable[train_index]) = match sim_res {
534-
Ok(sim) => (sim, true),
535-
// TODO: only make HTTP status code errors non-fatal
536-
Err(core_error) => (SimulationResponse::SimulationFailed { core_error }, false),
545+
let mut to_cache = vec![];
546+
for (train_hash, sim_res) in futures_hash.iter().zip(simulated) {
547+
let train_indexes = &to_sim[**train_hash];
548+
match sim_res {
549+
Ok(sim_res) => {
550+
to_cache.push((train_hash, sim_res.clone()));
551+
train_indexes
552+
.iter()
553+
.for_each(|index| simulation_results[*index] = sim_res.clone())
554+
}
555+
Err(core_error) => train_indexes.iter().for_each(|index| {
556+
simulation_results[*index] = SimulationResponse::SimulationFailed {
557+
core_error: core_error.clone(),
558+
}
559+
}),
537560
}
538561
}
539562

540-
let to_cache: Vec<_> = futures_index_hash
541-
.into_iter()
542-
.filter(|&(train_index, _)| is_cacheable[train_index])
543-
.map(|(train_index, train_hash)| (train_hash, &simulation_results[train_index]))
544-
.collect();
545-
546563
// Cache the simulation response
547564
valkey_conn.json_set_bulk(&to_cache).await?;
548565

0 commit comments

Comments
 (0)