influxdata/influxdb · error
no duplicate tag
Error message
no duplicate tag
What it means
This is a panic raised by an `.expect("no duplicate tag")` on `self.table.tag_columns.insert(id, tag_col)` while adding a tag column to a table in the InfluxDB3 catalog (influxdb3_catalog::catalog::versions::v2::update). The insert returns an error only if the map already holds a column under the same table-wide unique id, which the update transaction logic assumes can never happen. The library throws it because a violation means the in-memory TableDefinition has diverged from its log — i.e. catalog corruption — rather than a recoverable condition.
Solutions
- Check whether the catalog batch was applied more than once (look for duplicated ops in the write-ahead log / sequence numbers) and remove the duplicate replay.
- Verify the catalog file or snapshot is not corrupted or hand-edited; restore from a known-good backup.
- Ensure the column id comes from next_legacy_column_id() and that no other code path inserts into tag_columns with a manually chosen id.
- If reproducible, capture the failing catalog batch and file an issue with the influxdb3 catalog maintainers — this is an internal invariant failure, not user input validation.
Example fix
// before
self.table.tag_columns.insert(id, Arc::clone(&tag_col)).expect("no duplicate tag");
// after
if self.table.tag_columns.contains_key(&id) {
return Err(Error::DuplicateTagName { table: self.table.name.clone(), tag: tag.into() });
}
self.table.tag_columns.insert(id, Arc::clone(&tag_col)).expect("no duplicate tag"); Defensive patterns
Strategy: validation
Validate before calling
// Before replaying/applying a batch, check the tag isn't already a column
if table_def.columns().any(|c| c.name() == tag_name) {
return Err("tag column already exists in table; skip duplicate batch op");
} Type guard
fn tag_exists(table: &TableDefinition, id: &TableId) -> bool { table.tag_columns.contains_key(id) } Prevention
- Never apply the same catalog batch twice; track applied sequence numbers.
- Always allocate column ids via next_legacy_column_id(), never hand-assign them.
- Restore catalogs only from complete, consistent snapshots.
When it happens
Trigger: Calling the add-tag-column path of a TableTransaction (e.g. add_tag_column) when the generated table-unique column id (from next_legacy_column_id) or the tag name already exists in table.tag_columns. This can happen if the same catalog batch is applied twice (replay of a non-idempotent update) or if id allocation is skipped/duplicated in custom catalog code.
Common situations: Catalog snapshot/compaction bugs where a batch is replayed on top of an already-applied state; restoring a catalog file into a newer/older schema version; hand-edited or partially-written catalog protobuf files; concurrent writers bypassing the catalog transaction lock.
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
- field does not exist
- field family ID and name don't exist
- no duplicate column
- auto field family exists
- column id in series key should be valid
AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19).
Data as JSON: /api/errors/47fa46044808d060.
Report an issue: GitHub.
Appendix: source
Thrown at influxdb3_catalog/src/catalog/versions/v2/update.rs:1649
NUM_TAG_COLUMNS_LIMIT,
));
}
self.check_columns_limit()?;
self.table.check_name(tag.as_ref())?;
let id = self.table.tag_columns.next_id();
// Only increment if id is less than MAX.
if id < TagId::MAX {
self.table.tag_columns.set_next_id(id.next())
}
let col_id = self.next_legacy_column_id()?;
let tag_col = Arc::new(TagColumn::new(id, col_id, tag.as_ref()));
self.table
.tag_columns
.insert(id, Arc::clone(&tag_col))
.expect("no duplicate tag");
let col_def = ColumnDefinition::Tag(Arc::clone(&tag_col));
self.table
.columns
.insert(col_def.clone())
.expect("no duplicate column");
self.column_definitions.push(col_def.into());
Ok(tag_col)
}
pub(crate) fn add_time(&mut self) -> Result<Arc<TimestampColumn>> {
if self.table.timestamp_column.is_some() {
return Err(CatalogError::DuplicateColumn {
name: TIME_COLUMN_NAME.into(),
existing: InfluxColumnType::Timestamp,
});View on GitHub (pinned to 06200ef96b)