8000 Fix poor message search performance by nuno-vieira · Pull Request #680 · GetStream/stream-chat-swiftui · GitHub
[go: up one dir, main page]

Skip to content

Fix poor message search performance #680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 5, 2024

Conversation

nuno-vieira
Copy link
Member
@nuno-vieira nuno-vieira commented Dec 5, 2024

🔗 Issue Links

Fixes IOS-574

🎯 Goal

Fixes the poor performance of Message Search.

🛠 Implementation

Problem

The root cause of the issue was in the updateMessageSearchResults() function. We were accessing the channelController.channel for every search result. The problem with this is that we call startObservers() for every channel property access, which performs a Core Data fetch and, in this case, on the main thread.

We can see this from the time profiler:
image

Besides this, we were calling updateMessageSearchResults() twice, for every completion block performed in the loadNextMessages() and also in the didUpdateMessages() delegate calls.

First Solution

My first idea was to follow the UIKit approach. There, we use the dataStore to fetch the channel directly. However, this also does not work for SwiftUI. The reason is that for UIKit, the dataStore is accessed when the cell is reused, whereas in SwiftUI, we map all the results at once, and so we access the dataStore.channel for every result again, which means we convert the DTO to model on the main thread multiple times.

Profile result:
image

So, to avoid converting the channel models every time, I implemented a cache that would reuse the existing channels instead of always doing the DTO -> Model conversion.

This worked and the performance was great, but it does require an additional cache, which can increase the memory and is dependent on the amount of different channels we have in the result. So the final solution (check next section) is probably the better one.

Final Solution

Eventually, I realised that I could simply use the original implementation but offload the search results mapping to a background thread. This worked flawlessly, and it has the less amount of changes and does not require any additional caching.

Result (No hangs, and Main thread not busy):
image

🧪 Testing

Perform a message search in the Channel List. For example, search for "Hey".

Known Issue: The channel name is not rendered for some search results. This is an existing issue and out of the scope of this PR.

🎨 Changes

Before After
ScreenRecording_12-05-2024.01-13-47_1.MP4
ScreenRecording_12-05-2024.00-49-49_1.MP4

☑️ Checklist

  • I have signed the Stream CLA (required)
  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Affected documentation updated (docusaurus, tutorial, CMS (task created)

@nuno-vieira nuno-vieira added the bug Something isn't working label Dec 5, 2024
@nuno-vieira nuno-vieira requested a review from a team as a code owner December 5, 2024 10:42
loadingSearchResults = true
messageSearchController?.search(query: query, completion: { [weak self] _ in
messageSearchController?.search(text: searchText) { [weak self] _ in
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the search(query:) with search(text:) since it is the same thing but with some optimizations

Comment on lines -112 to -126
extension ChatMessage {

func makeChannelSelectionInfo(with chatClient: ChatClient) -> ChannelSelectionInfo? {
if let channelId = cid,
let channel = chatClient.channelController(for: channelId).channel {
let searchResult = ChannelSelectionInfo(
channel: channel,
message: self
)
return searchResult
} else {
return nil
}
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the extension because it could be used in other places , and since the performance it not good, better to not make it easy to use.

@Stream-SDK-Bot
Copy link
Collaborator
Stream-SDK-Bot commented Dec 5, 2024

SDK Size

title develop branch diff status
StreamChatSwiftUI 8.09 MB 8.1 MB +16 KB 🟢

Copy link
Contributor
@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ✅ Left 2 minor comments.

CHANGELOG.md Outdated
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming

### 🔄 Changed
- Fix poor message search performance [#680](https://github.com/GetStream/stream-chat-swiftui/pull/680)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Improve message search performance" sounds better?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Done

queue.async {
let results: [ChannelSelectionInfo] = messageSearchController.messages.compactMap { message in
guard let channelId = message.cid else { return nil }
guard let channel = messageSearchController.client.channelController(for: channelId).channel else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have to go through the message search controller here? We have the client injected in the VM.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes you are correct 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done ✅

Copy link
sonarqubecloud bot commented Dec 5, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
69.2% Coverage on New Code (required ≥ 70%)

See analysis details on SonarQube Cloud

Copy link
Contributor
@laevandus laevandus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With how DB observer work at the moment then this is a way to go.

@nuno-vieira nuno-vieira merged commit 6525d6b into develop Dec 5, 2024
11 of 12 checks passed
@nuno-vieira nuno-vieira deleted the fix/improve-message-search-performance branch December 5, 2024 12:18
@Stream-SDK-Bot Stream-SDK-Bot mentioned this pull request Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0