clockworklabs/SpacetimeDB · error

reducer transaction update {request_id} ran out of energy

Error message

reducer transaction update {request_id} ran out of energy

What it means

recv_reducer_update (crates/testing/src/modules.rs:155) reports EventStatus::OutOfEnergy: the host energy budget allotted to the transaction was exhausted before the reducer finished, so the transaction aborted. Energy in SpacetimeDB is a deterministic metering budget charged per row read/written and per operation; once exhausted, the reducer stops and no effects commit.

Source

Thrown at crates/testing/src/modules.rs:155

            .ok_or_else(|| anyhow::anyhow!("client receiver closed before reducer update {request_id}"))?;
        let OutboundMessage::V1(SerializableMessage::TxUpdate(update)) = message else {
            anyhow::bail!("expected reducer transaction update {request_id}, got {message:?}");
        };
        let Some(event) = update.event else {
            anyhow::bail!("expected full reducer transaction update {request_id}, got light update");
        };
        if event.request_id != Some(request_id) {
            anyhow::bail!(
                "expected reducer transaction update {request_id}, got request id {:?}",
                event.request_id
            );
        }
        match &event.status {
            EventStatus::Committed(_) => Ok(()),
            EventStatus::FailedUser(err) | EventStatus::FailedInternal(err) => {
                anyhow::bail!("reducer transaction update {request_id} failed: {err}")
            }
            EventStatus::OutOfEnergy => anyhow::bail!("reducer transaction update {request_id} ran out of energy"),
        }
    }

    pub async fn read_log(&self, size: Option<u32>) -> String {
        let bytes = self
            .client
            .module()
            .database_logger()
            .tail(size, false)
            .await
            .unwrap()
            .try_collect::<BytesMut>()
            .await
            .expect("failed to collect log stream");
        String::from_utf8(bytes.into()).unwrap()
    }

    async fn module_host(&self) -> ModuleHost {

View on GitHub (pinned to fb7282411b)

Solutions

  1. Raise the energy budget for the test host/database in your test setup so the measured work fits with headroom.
  2. Reduce work in the reducer: replace table scans with index lookups, batch inserts, or cap the number of rows processed per call.
  3. Confirm the cause by checking that the same call fails deterministically (energy is deterministic, so a retry alone never helps).
  4. Use module.read_log / energy reporting to see where the budget went before optimizing blindly.

Example fix

// before: bulk reducer exhausts default budget
module.call_reducer_json("import_all", &args).await?;
// after: chunk the work so each transaction fits the budget
for chunk in rows.chunks(1000) {
    module.call_reducer_json("import_chunk", &chunk_args(chunk)).await?;
    // await each update so failures surface immediately
}
Defensive patterns

Strategy: try-catch

Try / catch

// Catch out-of-energy, raise the budget once, re-run; energy is deterministic so nothing else helps.
match module.recv_reducer_update(rid).await {
    Ok(()) => {}
    Err(e) if e.to_string().contains("out of energy") => {
        increase_energy_budget(&mut host); // your test-host config knob
        module.call_reducer_json("import_all", &args).await?;
        module.recv_reducer_update(new_rid).await?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A test reducer iterates a large table (full scan in a loop), inserts/updates many rows in one call, or recurses; the energy budget configured for the test host is smaller than the work performed, so the transaction runs dry mid-execution.

Common situations: Tests that grew their fixture size over time until crossing the budget; bulk-import reducers exercised in CI with a default low budget; accidental unbounded while loops in module code that burn the budget deterministically.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@fb7282411b (2026-08-20). Data as JSON: /api/errors/c57067889084564b. Report an issue: GitHub.