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.
Start with the smallest source
Section titled “Start with the smallest source”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.
Bound every collection
Section titled “Bound every collection”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.
Keep work selective
Section titled “Keep work selective”One query executes against the configured database. Prefer bounded summaries and lazy detail, and release observers when a view unmounts.
Design indexes from real read paths
Section titled “Design indexes from real read paths”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.
Treat local queries as real work
Section titled “Treat local queries as real work”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.
Give agents explicit budgets
Section titled “Give agents explicit budgets”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.
Measure the whole path
Section titled “Measure the whole path”When a view is slow, separate:
- Database activation and snapshot restore.
- Catch-up synchronization.
- Local query compilation and initial evaluation.
- Incremental maintenance after changes.
- 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.