Skip to content

chore: add compound index { tmid: 1, ts: -1 } to messages collection - #40407

Open
IgorOhrimenko wants to merge 2 commits into
RocketChat:developfrom
IgorOhrimenko:chore/messages-tmid-ts-compound-index
Open

IgorOhrimenko wants to merge 2 commits into
RocketChat:developfrom
IgorOhrimenko:chore/messages-tmid-ts-compound-index

Conversation

@IgorOhrimenko

@IgorOhrimenko IgorOhrimenko commented May 5, 2026

Copy link
Copy Markdown

Proposed changes (including videos or screenshots)

Adds a compound index { tmid: 1, ts: -1 } (sparse) on the rocketchat_message collection.

Why. The current tmid_1 index (sparse) covers thread membership lookups, but it does not satisfy the sort: { ts: -1 } clause used by paginated thread reply loads (chat.getThreadMessages, REST /api/v1/chat.getThreadMessages, and the corresponding find({ tmid }).sort({ ts: -1 }).limit(N) calls). On large workspaces MongoDB's planner can pick IXSCAN { ts: 1 } instead — walking the global time index from newest backwards and filtering by tmid in memory. This works fine when the messages collection is small, but as it grows into the millions of documents the same query repeatedly degrades into a near-full collection scan. The planner is also seen to flap (replanned: true, replanReason: "cached plan was less efficient") between the two indexes, which causes intermittent multi-second response times for any client opening a thread.

The new compound index covers both the equality filter on tmid and the descending sort on ts, so the query is served entirely from the index and a single bounded scan returns exactly the requested page.

The index is sparse, so only thread replies (messages with tmid) are indexed — the storage cost on the (much larger) set of regular messages without tmid is zero.

Benchmark — production workspace, rocketchat_message ≈ 5M documents (~5.2 GB)

Heaviest thread on this workspace contains 957 replies. Same query (find({ _hidden: { $ne: true }, tmid: <id> }).sort({ ts: -1 })) with three plans forced via hint(...), three different limit values to show how each plan scales:

Plan (forced via hint) limit keysExamined executionMs
ts_1 (planner's choice on this WS) 50 81 889 167
tmid_1 (current upstream index) 50 957 3
tmid_1_ts_-1 (this PR) 50 50 0
ts_1 10 81 843 169
tmid_1 10 957 3
tmid_1_ts_-1 (this PR) 10 10 0
ts_1 none 5 053 665 9 405
tmid_1 none 957 3
tmid_1_ts_-1 (this PR) none 957 2

docsExamined is omitted because for this query it is always equal to keysExamined — the _hidden: { $ne: true } predicate is not covered by any of the candidate indexes, so every matched key requires a document fetch.

What the table shows:

  • ts_1scales with the messages collection: limit barely matters, every page costs the same overscan, and the no-limit query scans the entire 5M-row collection (9.4 s).
  • tmid_1 — scales with the thread size: it can find the right rows but cannot honour sort: { ts: -1 } from the index, so all 957 thread documents are fetched and sorted in memory regardless of pagination.
  • tmid_1_ts_-1 — scales with the page size: keysExamined == limit. This is the only plan where opening a thread costs O(page) rather than O(thread) or O(collection).

For threads where the planner picks ts_1 and gets unlucky (replanning), real production slow-query log entries on the same workspace report keysExamined: 5,049,435 / docsExamined: 5,049,435 for a single thread page load — i.e. an effective full collection scan.

Issue(s)

Open reports about slow queries on rocketchat_message:

Steps to test or reproduce

On any workspace large enough that rocketchat_message is in the millions, pick a thread with at least a few hundred replies and run:

const tmid = "<some_thread_root_id>";

// What the planner can pick today on large collections — overscan
db.rocketchat_message
  .find({ _hidden: { $ne: true }, tmid })
  .sort({ ts: -1 }).limit(50)
  .hint({ ts: 1 })
  .explain("executionStats").executionStats;

// Default behaviour (no hint) — after this PR the planner will pick tmid_1_ts_-1
db.rocketchat_message
  .find({ _hidden: { $ne: true }, tmid })
  .sort({ ts: -1 }).limit(50)
  .explain("executionStats").executionStats;

totalKeysExamined should equal the requested page size. End-to-end test: open the same thread in the client and observe time-to-first-reply render.

Further comments

  • The change mirrors prior small index additions to this collection (e.g. chore: add index for the files attribute on the messages collection #38087 for files._id).
  • No migration is required — modelIndexes() is reconciled at startup; createIndex is idempotent on existing keys.
  • Sparse on the same key set as the existing tmid_1 index, so the additional storage cost is bounded by the number of threaded replies (a small fraction of total messages on typical workspaces).

Summary by CodeRabbit

  • Bug Fixes
    • Improved database performance for paginated thread reply loading on large workspaces, resulting in faster response times and smoother navigation when browsing long message threads.

Speeds up paginated thread reply loads (chat.getThreadMessages) on
large workspaces. Without this index the query planner can pick an
IXSCAN over ts_1 and filter by tmid in memory, which becomes a near
full collection scan once the messages collection grows large.
@dionisio-bot

dionisio-bot Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.9.0, but it targets 8.5.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@CLAassistant

CLAassistant commented May 5, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 03427341-ea23-4487-a098-bffd5e10bdb6

📥 Commits

Reviewing files that changed from the base of the PR and between 07d9311 and 79ac06a.

📒 Files selected for processing (1)
  • packages/models/src/models/Messages.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/models/src/models/Messages.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: cubic · AI code reviewer

Walkthrough

Adds a sparse compound index { tmid: 1, ts: -1 } to the messages collection for paginated thread reply queries and documents the patch-level release change.

Changes

Thread Reply Pagination Index

Layer / File(s) Summary
Compound index and release note
packages/models/src/models/Messages.ts, .changeset/compound-index-thread-replies.md
Adds the sparse compound index alongside { tmid: 1 } and documents the patch-level change for @rocket.chat/meteor.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Suggested labels: type: chore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a compound index to the messages collection for threaded replies.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • TMID_1_TS_-1: Request failed with status code 401

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@changeset-bot

changeset-bot Bot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 79ac06a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@IgorOhrimenko
IgorOhrimenko marked this pull request as ready for review May 5, 2026 15:03
@IgorOhrimenko
IgorOhrimenko requested a review from a team as a code owner May 5, 2026 15:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/models/src/models/Messages.ts (1)

72-72: ⚡ Quick win

Remove inline implementation comment from index definition

Please drop the inline // used for ... comment and keep the index entry self-contained to match repo style for TS implementation files.

As per coding guidelines: "**/*.{ts,tsx,js} ... Avoid code comments in the implementation".

🤖 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/models/src/models/Messages.ts` at line 72, Remove the trailing
inline comment on the index definition so the entry is self-contained; locate
the index object that contains { key: { tmid: 1, ts: -1 }, sparse: true } in
Messages.ts (the index definition used for paginated thread reply loads /
chat.getThreadMessages) and delete the inline "// used for paginated thread
reply loads (chat.getThreadMessages)" comment, leaving only the index object per
repo TS style.
🤖 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/models/src/models/Messages.ts`:
- Line 72: Remove the trailing inline comment on the index definition so the
entry is self-contained; locate the index object that contains { key: { tmid: 1,
ts: -1 }, sparse: true } in Messages.ts (the index definition used for paginated
thread reply loads / chat.getThreadMessages) and delete the inline "// used for
paginated thread reply loads (chat.getThreadMessages)" comment, leaving only the
index object per repo TS style.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31df4de8-7679-47b4-9f98-b3788c5fbcd2

📥 Commits

Reviewing files that changed from the base of the PR and between 09933be and 07d9311.

📒 Files selected for processing (2)
  • .changeset/compound-index-thread-replies.md
  • packages/models/src/models/Messages.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/models/src/models/Messages.ts
🧠 Learnings (3)
📚 Learning: 2026-03-16T21:50:37.589Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39676
File: .changeset/migrate-users-register-openapi.md:3-3
Timestamp: 2026-03-16T21:50:37.589Z
Learning: For changes related to OpenAPI migrations in Rocket.Chat/OpenAPI, when removing endpoint types and validators from rocket.chat/rest-typings (e.g., UserRegisterParamsPOST, /v1/users.register) document this as a minor changeset (not breaking) per RocketChat/Rocket.Chat-Open-API#150 Rule 7. Note that the endpoint type is re-exposed via a module augmentation .d.ts in the consuming package (e.g., packages/web-ui-registration/src/users-register.d.ts). In reviews, ensure the changeset clearly states: this is a non-breaking change, the major version should not be bumped, and the changeset reflects a minor version bump. Do not treat this as a breaking change during OpenAPI migrations.

Applied to files:

  • .changeset/compound-index-thread-replies.md
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • packages/models/src/models/Messages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • packages/models/src/models/Messages.ts
🪛 LanguageTool
.changeset/compound-index-thread-replies.md

[style] ~5-~5: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...ring by tmid in memory, which becomes very expensive on collections with millions of message...

(EN_WEAK_ADJECTIVE)

🔇 Additional comments (1)
.changeset/compound-index-thread-replies.md (1)

1-5: Changeset entry looks good

Patch bump and rationale are clear, and the note accurately describes the index change and its performance impact.

@IgorOhrimenko

Copy link
Copy Markdown
Author

@coderabbitai Thanks — addressed in 79ac06a: the inline // used for ... comment was removed from the { tmid: 1, ts: -1 } index entry, so it now matches the repo TS style. The rationale stays documented in the PR description and the changeset.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@IgorOhrimenko Thanks for addressing this. Removing the inline comment keeps the index declaration consistent with the surrounding TypeScript style, while retaining the rationale in the PR description and changeset.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

@ggazzo
ggazzo requested a review from sampaiodiego July 27, 2026 18:52
@ggazzo ggazzo added this to the 8.8.0 milestone Jul 27, 2026
@sampaiodiego

Copy link
Copy Markdown
Member

thanks for your contribution @IgorOhrimenko :)

do you mind testing it out how it behaves if you replace the current tmid index by your compound index? since they share the same starting field, having just one should be enough

@scuciatto scuciatto modified the milestones: 8.8.0, 8.9.0 Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants