Skip to content

Query performance

Ramose queries are incremental and relational, but they still obey physical limits. Performance comes from reducing the candidate set early and bounding every collection.

Choose the most selective entity or trait as the query root. Apply equality and range predicates before traversing relationships. Project only the fields and nested data the view consumes.

projectDb.query
.from(Task)
.where({ status: "open" })
.where({ assignee: viewer.id })
.orderBy(Task.updatedAt, "desc")
.select({ id: Task.id, title: Task.title, updatedAt: Task.updatedAt })
.limit(50).after(null)

Avoid fetching full entities “just in case.” A narrow projection reduces server work, transfer size, local indexes, React updates, and agent context.

Every list needs a deterministic order and page size. Use the opaque continuation cursor returned with a page; do not construct offsets or infer cursor contents.

Nested collections need their own limits. Recursive rules need an explicit maximum depth. If the product truly needs every record, make that an export or background workflow rather than an interactive query.

One query executes against the configured database. Prefer bounded summaries and lazy detail, and release observers when a view unmounts.

Record representative queries before adding indexes. Favor indexes that support a selective predicate followed by the requested order. Too many speculative indexes raise write, storage, snapshot, and browser activation cost.

Watch for:

  • Filters on unindexed high-cardinality fields.
  • Sorting a broad set after weak filtering.
  • Traversing a relationship before reducing candidates.
  • Wide projections with nested collections.
  • Many live queries that differ only cosmetically.

Share a query definition when several components need the same result shape; derive small presentation changes in React.

Offline-capable queries execute against a persistent browser replica. They avoid network latency, not computation. Broad queries consume CPU and maintain indexes as new facts arrive.

Keep inactive views dormant. A component that renders 100 rows should not establish a separate live query for each row. Constructing a query is cheap; observing it activates synchronization.

MCP query limits protect the service and keep results useful. Set conservative caps for page size, depth, selected fields, execution time, and total work. Return query_budget_exceeded with actionable categories, never internal plans or data-dependent counts that reveal inaccessible records.

An agent should narrow and retry rather than request an exception. This is why catalog descriptions should mention useful filter fields and common relationships.

When a view is slow, separate:

  1. Database activation and snapshot restore.
  2. Catch-up synchronization.
  3. Local query compilation and initial evaluation.
  4. Incremental maintenance after changes.
  5. React rendering.

Changing the query will not fix a component that activates 200 observers; changing the component will not fix a full scan. Diagnose the stage before redesigning the model.