perf(llc): Reduce the number of read message per channel from DB when paginating (part 1)#2679
perf(llc): Reduce the number of read message per channel from DB when paginating (part 1)#2679VelikovPetar wants to merge 8 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR moves pagination and userId filtering into SQL: Message pagination resolves cursor ids to createdAt and applies cursor/limit in Drift queries; Reaction and PinnedMessageReaction DAOs centralize joined queries and apply messageId+userId predicates in SQL. Tests and changelog updated. ChangesDatabase Query Filtering and Pagination Optimization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/stream_chat_persistence/lib/src/dao/message_dao.dart (1)
195-200: 💤 Low valueAsymmetric cursor semantics preserved — worth documenting.
lessThantranslates to a strict<(isSmallerThanValue) whilegreaterThantranslates to>=(isBiggerOrEqualValue). This mirrors the legacy in-memory behavior ingetThreadMessagesByParentId(lines 141–156:removeRange(lessThanIndex, ...)excludes the cutoff,removeRange(0, greaterThanIndex)keeps it), so parity is preserved — but the SQL now makes the inconsistency very explicit, and thePaginationParams.greaterThanname reads as strict.Consider a short doc comment on
getMessagesByCidclarifying thatgreaterThanis inclusive of the cursor message andlessThanis exclusive, so future readers don't "fix" one side and break parity.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_chat_persistence/lib/src/dao/message_dao.dart` around lines 195 - 200, Add a short doc comment to getMessagesByCid explaining the asymmetric cursor semantics: lessThan (mapped to messages.createdAt.isSmallerThanValue) is exclusive while greaterThan (mapped to messages.createdAt.isBiggerOrEqualValue) is inclusive of the cursor message, matching the legacy in-memory behavior in getThreadMessagesByParentId (removeRange usage); mention PaginationParams.greaterThan and lessThan explicitly so future maintainers understand why one side is strict and the other is inclusive and don't accidentally change parity.packages/stream_chat_persistence/test/src/dao/message_dao_test.dart (1)
28-94: 💤 Low valueNit:
DateTime.now()inbaseTimestill gives non-deterministic absolute times.The monotonic 1-second spacing fixes ordering ties as the comment explains, but
baseTime = DateTime.now()is still wall-clock dependent — tests that compare against absolute timestamps (or run near a DST/leap boundary in some environments) could become flaky in the future. Consider pinningbaseTimeto a fixedDateTime.utc(...)constant to make the suite fully deterministic.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_chat_persistence/test/src/dao/message_dao_test.dart` around lines 28 - 94, Replace the wall-clock dependent baseTime assignment with a deterministic fixed UTC timestamp so generated Message createdAt values are stable; specifically change the baseTime variable (used to set createdAt in the Message/quotedMessages/threadMessages lists) from DateTime.now() to a constant UTC datetime (for example 2020-01-01T00:00:00Z) so tests no longer depend on current time or local DST/leap boundaries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/stream_chat_persistence/lib/src/dao/message_dao.dart`:
- Around line 195-200: Add a short doc comment to getMessagesByCid explaining
the asymmetric cursor semantics: lessThan (mapped to
messages.createdAt.isSmallerThanValue) is exclusive while greaterThan (mapped to
messages.createdAt.isBiggerOrEqualValue) is inclusive of the cursor message,
matching the legacy in-memory behavior in getThreadMessagesByParentId
(removeRange usage); mention PaginationParams.greaterThan and lessThan
explicitly so future maintainers understand why one side is strict and the other
is inclusive and don't accidentally change parity.
In `@packages/stream_chat_persistence/test/src/dao/message_dao_test.dart`:
- Around line 28-94: Replace the wall-clock dependent baseTime assignment with a
deterministic fixed UTC timestamp so generated Message createdAt values are
stable; specifically change the baseTime variable (used to set createdAt in the
Message/quotedMessages/threadMessages lists) from DateTime.now() to a constant
UTC datetime (for example 2020-01-01T00:00:00Z) so tests no longer depend on
current time or local DST/leap boundaries.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: beebe991-8702-448d-9aab-7f3181f9e824
📒 Files selected for processing (7)
packages/stream_chat_persistence/CHANGELOG.mdpackages/stream_chat_persistence/lib/src/dao/message_dao.dartpackages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dartpackages/stream_chat_persistence/lib/src/dao/reaction_dao.dartpackages/stream_chat_persistence/test/src/dao/message_dao_test.dartpackages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dartpackages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2679 +/- ##
==========================================
+ Coverage 65.27% 65.31% +0.03%
==========================================
Files 423 423
Lines 26622 26629 +7
==========================================
+ Hits 17377 17392 +15
+ Misses 9245 9237 -8 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| if (messagePagination != null) { | ||
| query.limit(messagePagination.limit); | ||
| } |
There was a problem hiding this comment.
Shouldn't we only limit this when either lessThanCutoff or greaterThanCutoff is not null? I don't know if it can happen that the target message is not in the cache, but in that case limiting the amount of messages might give issues?
There was a problem hiding this comment.
This actually matches the previous behaviour of the method. The limit was always applied, regardless of whether we have a lessThan/greaterThan ID supplied, and regardless of whether the corresponding message was found in the cache.
if (msgList.isNotEmpty) {
if (messagePagination?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination!.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
}
}
if (messagePagination?.greaterThan != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination!.greaterThan,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
}
}
if (messagePagination?.limit != null) {
return msgList
.skip(max(0, msgList.length - messagePagination!.limit))
.toList();
}
}
But now that you mentioned it, we actually have several smaller issues that neither the 'old' nor the 'new' implementation cover:
- Limit is used wrongly in the
greaterThanpath: We always take the last items from the fetch, which means that we skip the first messages which are after thegreaterThanID. IMO, we should retrieve thelimitamount of messages right after the cursor - We are missing handling for
greaterThanOrEqual/lessThanOrEqualin the method greaterThancase returns the cursor (the pivot message) as well, when it shouldn't
Should we fix these things in this PR? Or do you think it is better to handle them in a follow up?
There was a problem hiding this comment.
I think they can be fixed in this PR. You already change the method quite a bit, so would be good to make it correct.
There was a problem hiding this comment.
Ok, I will address this!
| // Strictly monotonic `createdAt` per message so SQL-side pagination | ||
| // filters (`WHERE createdAt < cutoff`, `ORDER BY createdAt ASC`) can't be | ||
| // confused by ties. Drift stores `DateTime` as integer Unix seconds by | ||
| // default, so the offset must be at least 1 second per row — otherwise | ||
| // sub-second offsets all round-trip onto the same second. |
There was a problem hiding this comment.
I wonder if we should migrate to storing in milliseconds instead of a datetime.
There was a problem hiding this comment.
This is actually something I already noted down to check after we do the offline storage optimisations.
In my opinion we should do this, as currently it seems like we might be losing some precision on dates (probably not that much of a big deal, but IMO it would be better to be precise as possible).
I think we should evaluate this separately, because I assume there would be other consequences of this change (maybe more storage consumed?)
There was a problem hiding this comment.
Yeah, better to do this separately
Submit a pull request
Linear: Part one of: FLU-485
CLA
Description of the pull request
PaginationParamsfrom DB when callingMessageDao.getMessagesByCidinstead of reading all messages for the channel and applying pagination in memory.userIdfrom DB when callingReactionDao.getReactionsByUserIdinstead of reading all reactions for the message and filtering in memory.userIdfrom DB when callingPinnedMessageReactionDao.getReactionsByUserIdinstead of reading all reactions for the message and filtering in memory.Screenshots / Videos
NA
Testing
Apply the given patch and run the test suites:
get_messages_by_cid_parity.dart- parity test check affirming no regressions in the new SQL first implementation against the old implementationget_messages_by_cid_bench_test.dart- bench test comparing the performance of the new vs old implementationsPatch
Summary by CodeRabbit
Bug Fixes
Performance
Tests
Documentation