nautechsystems/nautilus_trader · error · anyhow::Error

Failed to batch insert into block table: {e}

Error message

Failed to batch insert into block table: {e}

What it means

sqlx batch INSERT into the `block` table failed and was wrapped in anyhow with this message. The underlying Postgres error (constraint violation, type mismatch, connection loss, etc.) is embedded in `{e}`. This library throws it whenever the multi-row UNNEST-based block insert cannot execute against the pool.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:573

        )
        .bind(chain_id as i32)
        .bind(&numbers[..])
        .bind(&hashes[..])
        .bind(&parent_hashes[..])
        .bind(&miners[..])
        .bind(&gas_limits[..])
        .bind(&gas_useds[..])
        .bind(&timestamps[..])
        .bind(&base_fee_per_gases as &[Option<String>])
        .bind(&blob_gas_useds as &[Option<String>])
        .bind(&excess_blob_gases as &[Option<String>])
        .bind(&l1_gas_prices as &[Option<String>])
        .bind(&l1_gas_useds as &[Option<i64>])
        .bind(&l1_fee_scalars as &[Option<i64>])
        .execute(&self.pool)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to batch insert into block table: {e}"))
    }

    /// Inserts block timestamps observed while streaming pool events.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn add_pool_event_blocks_batch(
        &self,
        chain_id: u32,
        blocks: &[Block],
    ) -> anyhow::Result<()> {
        if blocks.is_empty() {
            return Ok(());
        }

        let chain_id_db = i32::try_from(chain_id)
            .with_context(|| format!("Chain ID {chain_id} exceeds PostgreSQL INTEGER"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped `{e}` source: it names the exact constraint, column, or cast that failed
  2. Handle unique violations by using ON CONFLICT DO UPDATE/NOTHING in the INSERT or deduplicating input rows by (chain_id, number) beforehand
  3. Verify all bound slice lengths are equal and value types match the column types (Option<String> for numerics kept as TEXT, i64 for integer columns)
  4. Check DB connectivity and schema: run the latest migrations and test `SELECT 1` via the same pool before bulk inserts

Example fix

// before: duplicate blocks abort the whole batch
sqlx::query("INSERT INTO block (chain_id, number, ...) VALUES (...)")
// after: tolerate re-processing
sqlx::query("INSERT INTO block (chain_id, number, ...) VALUES (...) ON CONFLICT (chain_id, number) DO NOTHING")
Defensive patterns

Strategy: validation

Validate before calling

// Rust: pre-validate rows before batch insert
anyhow::ensure!(!blocks.is_empty(), "no blocks to insert");
let distinct: std::collections::HashSet<_> = blocks.iter().map(|b| (b.chain_id, b.number)).collect();
anyhow::ensure!(distinct.len() == blocks.len(), "duplicate (chain_id, number) in batch");
// ensure pool is live before the bulk write
sqlx::query("SELECT 1").execute(&db.pool).await?;

Type guard

fn blocks_valid(blocks: &[BlockRow]) -> bool {
    blocks.iter().all(|b| b.number >= 0 && b.number <= i64::MAX)
        && blocks.iter().map(|b| (b.chain_id, b.number)).collect::<std::collections::HashSet<_>>().len() == blocks.len()
}

Try / catch

match db.insert_blocks_batch(&blocks).await {
    Ok(()) => tracing::debug!("inserted {} blocks", blocks.len()),
    Err(e) if e.to_string().contains("duplicate key") => tracing::warn!("blocks already present, skipping"),
    Err(e) => return Err(e.context("batch block insert failed")),
}

Prevention

When it happens

Trigger: Calling the batch block-insert method with rows violating schema constraints (duplicate primary key block_number/chain_id, NULL in a NOT NULL column), with values that fail Postgres casts (e.g. malformed decimal strings for gas prices), or when the connection pool is down/times out during `.execute(&self.pool)`.

Common situations: Re-running an indexer over overlapping block ranges causing unique-constraint conflicts; a schema migration drift where the local DB lacks a column the INSERT references; oversized i64 values overflowing; transient Postgres restarts or connection-pool exhaustion during bulk sync.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/184590293315ac61. Report an issue: GitHub.