Direct, account and hashtag timelines as two queries - #2174
Merged
Conversation
The last three timelines still asked the database for the page and the rows in one statement: a SELECT DISTINCT over the whole post -- content, source, details, cache, tags, to_array -- plus the joined author, sorted or hashed in full to choose twenty rows. Home, public, favourites, bookmarks, notifications and the list timeline had already moved to deciding the page over the one indexed column and reading exactly those rows afterwards; these follow, each as a *TimelineNids() method feeding streamsByNids(). Direct needs no DISTINCT (the recipient join fixes the viewer and the type, one row per post); the account page is DISTINCT only for the account reading its own profile, where the recipient join names no recipient; the hashtag page is DISTINCT over one integer where it used to be over the post. Performance.md is brought up to date: its two "still wants a transaction" items were already done (StreamActionsRequest::save() and ModerationRequest::save() both insert-then-catch-then-update), every getTimeline() branch is on the two-query path now, and what is left of the wide SELECT DISTINCT is named. Signed-off-by: Frank Karlitschek <frank@nextcloud.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank@nextcloud.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
karlitschek
force-pushed
the
perf/two-query-timelines
branch
from
September 14, 2026 06:24
3404b94 to
6ca0e05
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 10 of the fresh look. Performance.md's first "what to do next" said the timelines other than home and public still made the database sort or hash the whole post to deduplicate a page of twenty — and named notifications first. Reading the code: notifications, favourites, bookmarks and the list timeline had already moved. Direct, account and hashtag had not; this moves them.
What changes
Each becomes a
*TimelineNids()method that decides the page overs.nid— with every filter the old query applied (type, media, recipient, hidden and silenced actors, the viewer's follows for the hashtag page) — andstreamsByNids()reads exactly those rows, with the author, the viewer's actions and the boosted object joined:DISTINCT: the recipient join fixes the viewer and the typedm, one row per post.DISTINCTonly when the account reads its own profile (the recipient join then names no recipient and a post to several accounts would repeat); the public page is not.DISTINCTover one integer, where it used to be overcontent,source,details,cache,tagsandto_array.docs/Performance.mdis corrected where it had gone stale: the two "still wants a transaction" items (StreamActionsRequest::save(),ModerationRequest::save()) were already insert-then-catch-then-update; everygetTimeline()branch is now on the two-query path; what is left of the wideSELECT DISTINCTis named (searchContent(), the thread walk,getAnnouncesAndRepliesTo(), and the*_dep()methods behind the uncalled Custom Local API routes), and "what to do next" is rewritten accordingly.Verification
tests/Db/TwoQueryTimelinesTest.php(7 cases) pins the shape for the three: the wide read is handed the page and nothing else, an empty page makes no wide read, an account timeline without an account asks nothing.TimelineSeedTest,StreamFilterTest,MediaTypeTimelineTest,OnlyMediaTimelineTest,DomainBlockTimelineTest,SilencedInstanceTimelineTest,BoostVisibilityTestall read these probes against a real database. That is the run to watch on this PR.🤖 Generated with Claude Code