Skip to content

fix(plugin-mongodb): stop large collections hanging on open and make Stop cancel the query - #1999

Merged
datlechin merged 1 commit into
mainfrom
fix/mongodb-browse-hang
Jul 30, 2026
Merged

fix(plugin-mongodb): stop large collections hanging on open and make Stop cancel the query#1999
datlechin merged 1 commit into
mainfrom
fix/mongodb-browse-hang

Conversation

@datlechin

Copy link
Copy Markdown
Member

Reported in chat: opening a MongoDB collection with ~4.6M documents hangs on "Executing…" forever at page size 20, while Compass shows the first page instantly.

What was actually wrong

The obvious theory (the row count blocks the grid) is contradicted by the code. toolbarState.setExecuting(false) fires the moment the browse query returns, before phase 2 is even launched, so a stuck spinner proves the find({}).limit(20) itself never returned. The count was already deferred and already used the O(1) metadata read. The screenshot also rules out a restored filter: the Filters button renders a filled icon and a (N) count when filters are applied, and it shows neither.

Two defects made any non-instant MongoDB operation an unrecoverable hang:

  1. estimatedDocumentCount passed nil opts, so the configured maxTimeMS never reached the server on the one call that runs on every table open. Views and time-series collections have no metadata count, so the server falls back to executing the pipeline as a full scan, unbounded.
  2. Cancel was a no-op for exactly the calls that hang. It only flipped a flag checked between mongoc_cursor_next calls, so it could not touch a blocking count or the first cursor_next where an unindexed sort's scan happens.

The most likely trigger for this specific report is a sort on a non-_id field with no index. Per MongoDB's docs a sort + limit: 20 uses top-k, which buffers only 20 documents, so it never trips the 100MB guard and raises no error at all while still reading all 4.6M documents. It is indistinguishable from a count scan. I could not confirm which trigger fired without the reporter's database, so the fix targets the class: no MongoDB operation can hang past a bound, and every hang is cancellable and explicable.

Changes

Plugin (ships on a plugin-mongodb-v* republish, not with the app release)

  • estimatedDocumentCount now builds opts and carries maxTimeMS.
  • Background counts cap at 5s (matching Compass's COUNT_MAX_TIME_MS_CAP); user-initiated counts use the configured timeout.
  • Unfiltered countDocuments gets hint: "_id_" (MongoDB's documented mitigation), retried without the hint only on a bad-hint error, never on a timeout.
  • find runs inside a mongoc session whose lsid is copied under lock, so cancel sends killSessions from a separate control client. The connection's own queue is blocked inside the C call and cannot send anything. Degrades cleanly when the deployment has no session support.
  • executeUserQuery bounds the server-side limit instead of requesting PluginRowLimits.emergencyMax (5,000,000) and trimming client-side. It asks for rowCap + 1 so truncation stays detectable.
  • streamRows/streamFind honour skip and limit, which were silently dropped.
  • iterateCursorStreaming gained the truncation cap iterateCursor already had.
  • Server error code 50 maps to a message naming the timeout and the missing index.

App (ships with the app release)

  • New fetchExactRowCount requirement on PluginDatabaseDriver with a default implementation: additive, so no currentPluginKitVersion bump and no release-all-plugins.sh run. Already-built plugins keep loading.
  • The row-count task is tracked and cancelled by Stop.
  • Count exactly in the status bar runs a real count on demand, for an estimated total or one shown as ?.
  • A non-positive approximate count is treated as unknown rather than as an empty table. An exact zero is still trusted.

Verification

  • SwiftLint --strict clean on every changed file. The two force-unwrap hits in MongoDBConnection.swift are pre-existing on main (lines 241-242 there) and untouched.
  • All changed files pass swiftc -parse.
  • New pure-logic tests for the timeout and find-limit policies, plus regression tests for the count-state rule. The policy types are symlinked into TableProTests/PluginTestSources/ so the tests exercise the shipping code, not a copy. Worth knowing: BsonDocumentFlattenerTests.swift declares a private duplicate of the type it tests, so it validates a copy.
  • Not built. I do not build Xcode in this repo; please report any compile errors.

Notes for review

  • Table export is unaffected: it uses defaultExportQuery (find({}), no limit). The streaming export path receives baseQueryForMore, a query-tab concept, so honouring its limit is a fix rather than a regression.
  • "Count exactly" is a labelled link rather than making the ~4,600,000 text itself tappable. Splitting a localized format string to make one number clickable breaks translations and RTL.
  • Left out deliberately: gating the exact count on bytes-on-disk (the approach Sequel Ace uses to dodge the never-analyzed-table trap) needs a size signal that is not plumbed through the driver protocol, and index-aware sort validation, which needs another round trip on the same struggling collection and which Compass does not do either.

https://claude.ai/code/session_01DSrwXNZ4S4jQXftgC6E2hv

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mintlify

mintlify Bot commented Jul 30, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 30, 2026, 3:01 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@datlechin
datlechin merged commit 90a7075 into main Jul 30, 2026
3 checks passed
@datlechin
datlechin deleted the fix/mongodb-browse-hang branch July 30, 2026 15:12
@mintlify

mintlify Bot commented Jul 30, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟡 Building Jul 30, 2026, 2:59 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant