influxdata/influxdb · error
Existing transaction for table should not exist
Error message
Existing transaction for table should not exist
What it means
This is a panic from an .expect() on the result of inserting a TableTransaction into the in-flight batch's tables map. The library asserts that no transaction for the same table_id already exists in the map; if insert() reports a previous entry, the batch bookkeeping invariant (one open transaction per table per batch) has been violated. It indicates a logic bug in batch/table mutation paths, not user error.
Solutions
- Do not reuse a CatalogBatch after any operation has inserted a table transaction; build a fresh batch per logical operation
- Check that all write paths go through the public API once per table per batch; remove duplicate table_or_create/table_tx_or_create calls for the same table
- Verify thread-safety: only one thread should mutate a given CatalogBatch at a time
- If hit inside the library, file a bug with the write path that triggered it
Example fix
// before
let batch = CatalogBatch::new(...);
batch.table_or_create("cpu")?;
batch.table_or_create("cpu")?; // panics: transaction already inserted
// after
batch.table_or_create("cpu")?;
let tx = batch.table_tx_or_create("cpu")?; // reuse the existing transaction Defensive patterns
Strategy: validation
Validate before calling
// before inserting into a batch, ensure no open transaction exists for the table
if batch.tables.contains_key(&table_id) {
return Err(anyhow!("table {} already has an open transaction in this batch", table_id));
} Type guard
fn has_open_tx(batch: &CatalogBatch, table_id: TableId) -> bool {
batch.tables.contains_key(&table_id)
} Prevention
- Create each table at most once per CatalogBatch
- Never reuse or replay a partially mutated batch; build a fresh one per operation
- Do not share a &mut CatalogBatch across threads
- Keep table mutations through the public transaction APIs only
When it happens
Trigger: Calling table-level create/alter methods on the same CatalogBatch/TableBatch twice such that table_or_create returns a table_id already inserted into self.tables — e.g. creating a table, then re-entering the code path that inserts a transaction for the same table_id within one batch.
Common situations: Reused builder/batch objects after a failed operation; concurrent mutation of a CatalogBatch from multiple threads; plugin or extension code that calls table creation APIs twice with the same table name in one write path.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- column id in series key should be valid
- mapped to out-of-bounds shard
- no duplicate table by ID or name
- ordered catalog batch should contain changes
- ordered catalog batch should succeed when applied
AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19).
Data as JSON: /api/errors/739ecbf80621e2d7.
Report an issue: GitHub.
Appendix: source
Thrown at influxdb3_catalog/src/catalog/versions/v2/update.rs:1898
/// Get a mutable reference to a [TableTransaction] for the given table name.
///
/// If no table transaction exists and the table exists in the current `database_schema`,
/// a new [TableTransaction] is created and returned.
pub(crate) fn table_transaction(&mut self, name: &str) -> Option<&mut TableTransaction> {
if let Some(id) = self.tables.id_for_name(name) {
self.tables.get_mut_by_id(&id)
} else if let Some(def) = self.database_schema.table_definition(name) {
let id = def.table_id;
// start a transaction for this table
let tx = TableTransaction::new(
def.as_ref().clone(),
Arc::clone(&self.database_schema),
self.columns_per_table_limit,
self.storage_mode,
);
self.tables
.insert(tx.table.table_id, tx)
.expect("Existing transaction for table should not exist");
self.tables.get_mut_by_id(&id)
} else {
None
}
}
pub fn table_or_create(&mut self, table_name: &str) -> Result<TableId> {
match self.table_transaction(table_name) {
Some(tx) => Ok(tx.table.table_id),
None => self.create_table(
table_name,
create_table_columns::none(),
None,
FieldFamilyMode::Aware,
),
}
}
View on GitHub (pinned to 06200ef96b)