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(×tamps[..])
.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
- Read the wrapped `{e}` source: it names the exact constraint, column, or cast that failed
- Handle unique violations by using ON CONFLICT DO UPDATE/NOTHING in the INSERT or deduplicating input rows by (chain_id, number) beforehand
- 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)
- 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
- Use ON CONFLICT clauses so re-syncs are idempotent
- Deduplicate input rows by their primary key before every batch
- Keep all bound UNNEST slices the same length — assert before the call
- Run migrations at startup so the schema always matches the code
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
- Failed to insert into block table: {e}
- Failed to batch insert into pool_event_block table: {e}
- Failed to insert into dex table: {e}
- Failed to insert into pool table: {e}
- Failed to batch insert into pool table: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/184590293315ac61.
Report an issue: GitHub.