influxdata/influxdb · error

database should exist by id

Error message

database should exist by id

What it means

Panic from `.expect("database should exist by id")` on `inner.databases.get_by_id(&self.database_schema.id)` inside the commit path of a CatalogTransaction (after `apply_catalog_batch` succeeds in versions/v2/update.rs). The code assumes that a successfully applied batch referencing this database leaves the database present in the catalog's databases map; a None result means the apply silently dropped or failed to register the database — an unrecoverable internal inconsistency.

Solutions

  1. Check the catalog log for a delete-database op with a sequence number between the transaction's start and commit; re-order or drop the conflicting transaction.
  2. Verify apply_catalog_batch's handling of database upserts for this sequence range; patch or upgrade to a version fixing the replay bug.
  3. Restore the catalog from a snapshot that still contains the database.
  4. Capture the sequence numbers and failing batch and file an upstream issue — this indicates a serious catalog consistency bug.

Example fix

// before
inner.apply_catalog_batch(&catalog_batch, sequence, None).map(|_| {
    inner.databases.get_by_id(&self.database_schema.id).expect("database should exist by id")
})
// after
inner.apply_catalog_batch(&catalog_batch, sequence, None).and_then(|_| {
    inner.databases.get_by_id(&self.database_schema.id)
        .ok_or_else(|| Error::DatabaseMissingAfterCommit { id: self.database_schema.id })
})
Defensive patterns

Strategy: try-catch

Validate before calling

// Before committing, confirm the database still exists in the catalog
if catalog.databases().get_by_id(&db_schema.id).is_none() {
    return Err("database was dropped; aborting stale transaction");
}

Try / catch

// Wrap commit and handle the panic via catch_unwind at the replay boundary
let result = std::panic::catch_unwind(AssertUnwindSafe(|| tx.commit()));
match result {
    Ok(Ok(db)) => Ok(db),
    Ok(Err(e)) => Err(e),
    Err(_panic) => Err("catalog consistency failure during commit; restore from snapshot".into()),
}

Prevention

When it happens

Trigger: Committing a database-scoped catalog transaction (write_database / commit) where apply_catalog_batch succeeds but the databases map lacks `self.database_schema.id` — e.g. the batch deleted the database, applied ops out of order, or the database creation op was filtered out during replay.

Common situations: Catalog replay where a DROP DATABASE op precedes a pending transaction's ops; snapshots restored with the database missing; concurrent commits where one deletes while another commits writes; bugs in apply_catalog_batch's upsert logic.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/de31079cf8a30025. Report an issue: GitHub.

Appendix: source

Thrown at influxdb3_catalog/src/catalog/versions/v2/update.rs:1858

impl DatabaseCatalogTransaction {
    pub fn sequence_number(&self) -> CatalogSequenceNumber {
        self.catalog_sequence
    }

    /// Apply the current transaction to the [super::InnerCatalog] and return the updated [DatabaseSchema].
    ///
    /// # NOTE
    ///
    /// Used for testing purposes.
    pub fn apply_to_inner(&self, inner: &mut super::InnerCatalog) -> Result<Arc<DatabaseSchema>> {
        let (catalog_batch, sequence) = self.clone().catalog_batch();
        inner
            .apply_catalog_batch(&catalog_batch, sequence, None)
            .map(|_| {
                inner
                    .databases
                    .get_by_id(&self.database_schema.id)
                    .expect("database should exist by id")
            })
    }

    /// Check if the transaction is empty, meaning it has no pending operations.
    fn is_empty(&self) -> bool {
        self.ops.is_empty()
            && self.tables.resource_iter().all(|tx| {
                tx.column_definitions.is_empty() && tx.field_family_definitions.is_empty()
            })
    }

    fn catalog_batch(self) -> (CatalogBatch, CatalogSequenceNumber) {
        let s = self.catalog_sequence;
        (self.into(), s)
    }

    /// Check if a table exists by its name in the current transaction or in the database schema.
    fn table_exists_by_name(&self, name: &str) -> bool {

View on GitHub (pinned to 06200ef96b)