clockworklabs/SpacetimeDB · error · DBError::Other

Estimated cardinality ({estimate} rows) exceeds limit ({limi

Error message

Estimated cardinality ({estimate} rows) exceeds limit ({limit} rows)

What it means

Before executing SQL, handling a call, or registering a subscription, the node estimates how many rows the statements will affect or scan (summing per-query estimates) and compares the total against the database's configured row_limit; the check is skipped only for auth contexts that may exceed row limits. If the estimate exceeds the limit, the request is rejected up front to protect the node from oversized workloads.

Source

Thrown at crates/core/src/estimation.rs:27

/// If the caller is not allowed to exceed the row limit,
/// reject the request if the estimated cardinality exceeds the limit.
pub fn check_row_limit<Query>(
    queries: &[Query],
    db: &RelationalDB,
    tx: &Tx,
    row_est: impl Fn(&Query, &Tx) -> u64,
    auth: &AuthCtx,
) -> Result<(), DBError> {
    if !auth.exceed_row_limit()
        && let Some(limit) = db.row_limit(tx)?
    {
        let mut estimate: u64 = 0;
        for query in queries {
            estimate = estimate.saturating_add(row_est(query, tx));
        }
        if estimate > limit {
            return Err(DBError::Other(anyhow::anyhow!(
                "Estimated cardinality ({estimate} rows) exceeds limit ({limit} rows)"
            )));
        }
    }
    Ok(())
}

/// Use cardinality estimates to predict the total number of rows scanned by a query.
pub fn estimate_rows_scanned(tx: &Tx, plan: &PhysicalPlan) -> u64 {
    match plan {
        PhysicalPlan::TableScan(..) | PhysicalPlan::IxScan(..) => row_estimate(tx, plan),
        PhysicalPlan::Filter(input, _) => estimate_rows_scanned(tx, input).saturating_add(row_estimate(tx, input)),
        PhysicalPlan::NLJoin(lhs, rhs) => estimate_rows_scanned(tx, lhs)
            .saturating_add(estimate_rows_scanned(tx, rhs))
            .saturating_add(row_estimate(tx, lhs).saturating_mul(row_estimate(tx, rhs))),
        PhysicalPlan::IxJoin(IxJoin { lhs, unique: true, .. }, _) => {
            estimate_rows_scanned(tx, lhs).saturating_add(row_estimate(tx, lhs))
        }

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Split the work into smaller batches (chunked inserts, repeated calls).
  2. Narrow subscriptions with WHERE clauses so estimated matched rows stay under the limit.
  3. Raise the database's row limit in the server configuration if your deployment allows it.
  4. On builds whose auth supports it, run under an auth context permitted to exceed row limits.

Example fix

-- before: single giant statement
INSERT INTO ticks SELECT * FROM staging; -- estimate 10,000,000 rows
-- after: batched under the limit
INSERT INTO ticks SELECT * FROM staging LIMIT 100000; -- repeat per batch

// subscription (before): SELECT * FROM ticks;
// subscription (after):  SELECT * FROM ticks WHERE player_id = :me;
Defensive patterns

Strategy: validation

Validate before calling

async function safeSubscribe(db: DbConnection, query: string, limit: number): Promise<void> {
  // sanity-check expected size before subscribing
  const [{ n }] = await db.sql`SELECT COUNT(*) AS n FROM (${query.raw})`.then(r => r.rows as any);
  if (Number(n) > limit) throw new Error(`query would match ${n} rows > limit ${limit} — add a WHERE filter`);
  await db.subscriptionBuilder().subscribe([query]);
}

Try / catch

try {
  await db.sql`INSERT INTO ticks SELECT * FROM staging`;
} catch (e) {
  if (String(e).includes('exceeds limit')) {
    await insertInChunks('staging', 'ticks', chunkRows: 100_000); // retry with smaller batches
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: A multi-row INSERT or INSERT ... SELECT whose estimated cardinality exceeds row_limit; a subscription whose query plane matches more rows than allowed; a reducer call expected to write more rows in one transaction than the limit permits.

Common situations: Seeding or importing large tables in one statement; broad subscriptions like SELECT * FROM big_table; default row_limit too small for the workload; bulk jobs migrated from non-limited environments.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/704e740edfb157eb. Report an issue: GitHub.