{"record":{"id":"704e740edfb157eb","repo":"clockworklabs/SpacetimeDB","slug":"estimated-cardinality-estimate-rows-exceeds-li","errorCode":null,"errorMessage":"Estimated cardinality ({estimate} rows) exceeds limit ({limit} rows)","messagePattern":"Estimated cardinality \\((.+?) rows\\) exceeds limit \\((.+?) rows\\)","errorType":"exception","errorClass":"DBError","httpStatus":null,"severity":"error","filePath":"crates/core/src/estimation.rs","lineNumber":27,"sourceCode":"\n/// If the caller is not allowed to exceed the row limit,\n/// reject the request if the estimated cardinality exceeds the limit.\npub fn check_row_limit<Query>(\n    queries: &[Query],\n    db: &RelationalDB,\n    tx: &Tx,\n    row_est: impl Fn(&Query, &Tx) -> u64,\n    auth: &AuthCtx,\n) -> Result<(), DBError> {\n    if !auth.exceed_row_limit()\n        && let Some(limit) = db.row_limit(tx)?\n    {\n        let mut estimate: u64 = 0;\n        for query in queries {\n            estimate = estimate.saturating_add(row_est(query, tx));\n        }\n        if estimate > limit {\n            return Err(DBError::Other(anyhow::anyhow!(\n                \"Estimated cardinality ({estimate} rows) exceeds limit ({limit} rows)\"\n            )));\n        }\n    }\n    Ok(())\n}\n\n/// Use cardinality estimates to predict the total number of rows scanned by a query.\npub fn estimate_rows_scanned(tx: &Tx, plan: &PhysicalPlan) -> u64 {\n    match plan {\n        PhysicalPlan::TableScan(..) | PhysicalPlan::IxScan(..) => row_estimate(tx, plan),\n        PhysicalPlan::Filter(input, _) => estimate_rows_scanned(tx, input).saturating_add(row_estimate(tx, input)),\n        PhysicalPlan::NLJoin(lhs, rhs) => estimate_rows_scanned(tx, lhs)\n            .saturating_add(estimate_rows_scanned(tx, rhs))\n            .saturating_add(row_estimate(tx, lhs).saturating_mul(row_estimate(tx, rhs))),\n        PhysicalPlan::IxJoin(IxJoin { lhs, unique: true, .. }, _) => {\n            estimate_rows_scanned(tx, lhs).saturating_add(row_estimate(tx, lhs))\n        }","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/core/src/estimation.rs#L9-L45","documentation":"Before executing a batch of queries (typically client subscriptions), the server sums per-query cardinality estimates and compares the total against the database's configured row limit, skipping the check only for auth contexts allowed to exceed it. A batch whose estimate exceeds the limit is rejected up front, protecting the server from unbounded row materialization.","triggerScenarios":"Submitting subscriptions (e.g. SELECT * FROM big_table, or multi-table subscription sets) whose combined estimated row count exceeds the row limit configured for the database; ad-hoc SQL on a row-limited database without an exempt auth context.","commonSituations":"Hosted/free-tier row caps; subscribing SELECT * to growing tables; incrementally adding tables to a subscription until the estimate crosses the cap.","solutions":["Narrow the queries: add WHERE filters, subscribe to fewer tables, or select only needed rows/columns","Split the subscription set across multiple clients or requests so each stays under the per-request estimate","If you administer the server, raise or remove the row limit in the database configuration","For one-off inspection on self-hosted servers, temporarily raise the limit rather than fighting the estimator"],"exampleFix":"// before\nsubscriptionBuilder.subscribe([\"SELECT * FROM events\"]);   // whole table\n\n// after\nsubscriptionBuilder.subscribe([\"SELECT * FROM events WHERE ts > now() - interval '1 hour'\"]);","handlingStrategy":"validation","validationCode":"// Client-side: estimate before subscribing and narrow if too broad\nconst est = await db.db.connectionOneOff(`SELECT COUNT(*) FROM ${table} ${whereClause}`);\nif (Number(est) > ROW_LIMIT) throw new Error(`query would exceed row limit; add filters`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid SELECT * subscriptions on unbounded tables; always filter or paginate by key/time","Know the row limit of your tier/database and size subscription sets against COUNT(*) baselines","Grow subscriptions incrementally and load-test so estimates stay under the cap as data grows"],"tags":["query","limits","subscription","cardinality","server"],"backgroundTag":"row-limit-exceeded","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}