diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..3bdd2ed6 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-30 - [Performance] Parallelize initial space sync fetching +**Learning:** In space service synchronization, iterating over space rooms and asynchronously fetching avatar for each space using `await` sequentially causes blocking for I/O bounds when joining multiple spaces, potentially leading to performance bottlenecks during initial startup. +**Action:** Use `futures_util::future::join_all` to concurrently fetch avatar and information for all initial spaces, reducing latency and time spent initializing spaces list. Parallelizing space sync fetching and queuing preserves order while drastically reducing blocking. diff --git a/src/space_service_sync.rs b/src/space_service_sync.rs index c02bbc8a..1ae00e01 100644 --- a/src/space_service_sync.rs +++ b/src/space_service_sync.rs @@ -130,8 +130,11 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> { // Get the set of top-level (root) spaces that the user has joined. let (initial_spaces, mut spaces_diff_stream) = space_service.subscribe_to_top_level_joined_spaces().await; - for space in &initial_spaces { - add_new_space(space, &client).await; + let jsi_futures = futures_util::future::join_all( + initial_spaces.iter().map(|space| get_joined_space_info(space, &client)) + ).await; + for jsi in jsi_futures { + enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi)); } let mut all_joined_spaces: Vector = initial_spaces; if LOG_SPACE_SERVICE_DIFFS { log!("space_service: initial set: {all_joined_spaces:?}"); } @@ -224,8 +227,13 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> { match diff { VectorDiff::Append { values: new_spaces } => { if LOG_SPACE_SERVICE_DIFFS { log!("space_service: diff Append {}", new_spaces.len()); } + let jsi_futures = futures_util::future::join_all( + new_spaces.iter().map(|space| get_joined_space_info(space, &client)) + ).await; + for jsi in jsi_futures { + enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi)); + } for new_space in new_spaces { - add_new_space(&new_space, &client).await; all_joined_spaces.push_back(new_space); } } @@ -315,8 +323,11 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> { remove_space(&space); } enqueue_spaces_list_update(SpacesListUpdate::ClearSpaces); - for new_space in &new_spaces { - add_new_space(new_space, &client).await; + let jsi_futures = futures_util::future::join_all( + new_spaces.iter().map(|space| get_joined_space_info(space, &client)) + ).await; + for jsi in jsi_futures { + enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi)); } all_joined_spaces = new_spaces; } @@ -335,6 +346,11 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> { async fn add_new_space(space: &SpaceRoom, client: &Client) { + let jsi = get_joined_space_info(space, client).await; + enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi)); +} + +async fn get_joined_space_info(space: &SpaceRoom, client: &Client) -> JoinedSpaceInfo { let space_avatar_opt = if let Some(url) = &space.avatar_url { fetch_space_avatar(url.clone(), client) .await @@ -345,7 +361,7 @@ async fn add_new_space(space: &SpaceRoom, client: &Client) { || utils::avatar_from_room_name(Some(&space.display_name)) ); - let jsi = JoinedSpaceInfo { + JoinedSpaceInfo { space_name_id: RoomNameId::new( matrix_sdk::RoomDisplayName::Named(space.display_name.clone()), space.room_id.clone(), @@ -358,8 +374,7 @@ async fn add_new_space(space: &SpaceRoom, client: &Client) { world_readable: space.world_readable, guest_can_join: space.guest_can_join, children_count: space.children_count, - }; - enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi)); + } }