{"record":{"id":"c57067889084564b","repo":"clockworklabs/SpacetimeDB","slug":"reducer-transaction-update-request-id-ran-out-of","errorCode":null,"errorMessage":"reducer transaction update {request_id} ran out of energy","messagePattern":"reducer transaction update (.+?) ran out of energy","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/testing/src/modules.rs","lineNumber":155,"sourceCode":"            .ok_or_else(|| anyhow::anyhow!(\"client receiver closed before reducer update {request_id}\"))?;\n        let OutboundMessage::V1(SerializableMessage::TxUpdate(update)) = message else {\n            anyhow::bail!(\"expected reducer transaction update {request_id}, got {message:?}\");\n        };\n        let Some(event) = update.event else {\n            anyhow::bail!(\"expected full reducer transaction update {request_id}, got light update\");\n        };\n        if event.request_id != Some(request_id) {\n            anyhow::bail!(\n                \"expected reducer transaction update {request_id}, got request id {:?}\",\n                event.request_id\n            );\n        }\n        match &event.status {\n            EventStatus::Committed(_) => Ok(()),\n            EventStatus::FailedUser(err) | EventStatus::FailedInternal(err) => {\n                anyhow::bail!(\"reducer transaction update {request_id} failed: {err}\")\n            }\n            EventStatus::OutOfEnergy => anyhow::bail!(\"reducer transaction update {request_id} ran out of energy\"),\n        }\n    }\n\n    pub async fn read_log(&self, size: Option<u32>) -> String {\n        let bytes = self\n            .client\n            .module()\n            .database_logger()\n            .tail(size, false)\n            .await\n            .unwrap()\n            .try_collect::<BytesMut>()\n            .await\n            .expect(\"failed to collect log stream\");\n        String::from_utf8(bytes.into()).unwrap()\n    }\n\n    async fn module_host(&self) -> ModuleHost {","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/fb7282411b0b73cc68d51e161f180cd409abd851/crates/testing/src/modules.rs#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: bulk reducer exhausts default budget\nmodule.call_reducer_json(\"import_all\", &args).await?;\n// after: chunk the work so each transaction fits the budget\nfor chunk in rows.chunks(1000) {\n    module.call_reducer_json(\"import_chunk\", &chunk_args(chunk)).await?;\n    // await each update so failures surface immediately\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Catch out-of-energy, raise the budget once, re-run; energy is deterministic so nothing else helps.\nmatch module.recv_reducer_update(rid).await {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"out of energy\") => {\n        increase_energy_budget(&mut host); // your test-host config knob\n        module.call_reducer_json(\"import_all\", &args).await?;\n        module.recv_reducer_update(new_rid).await?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["spacetimedb","testing","reducer","energy-budget"],"backgroundTag":"energy-budget-exceeded","analyzedSha":"fb7282411b0b73cc68d51e161f180cd409abd851","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}