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
- Raise the energy budget for the test host/database in your test setup so the measured work fits with headroom.
- Reduce work in the reducer: replace table scans with index lookups, batch inserts, or cap the number of rows processed per call.
- Confirm the cause by checking that the same call fails deterministically (energy is deterministic, so a retry alone never helps).
- 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
- Set the test host's energy budget with generous headroom over the largest fixture you run.
- Cap row work per reducer call (chunk bulk operations) so single transactions stay far below budget.
- Treat recurring out-of-energy in CI as a signal to add an index or shrink the scan, not to keep raising the budget.
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
- reducer transaction update {request_id} failed: {err}
- reducer ran out of energy
- cannot serialize refs without a typespace
- cannot deserialize refs without a typespace
- There is already a reducer, procedure, or view with the name
AI-assisted analysis of clockworklabs/SpacetimeDB@fb7282411b (2026-08-20).
Data as JSON: /api/errors/c57067889084564b.
Report an issue: GitHub.