nautechsystems/nautilus_trader · error · anyhow::Error
Failed to batch insert into pool table: {e}
Error message
Failed to batch insert into pool table: {e} What it means
UNNEST-based batch insert of multiple pools into the `pool` table failed; wrapped with this message. This is the bulk variant of the single pool insert, where all column arrays are bound as slices and unnested in one statement.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:923
)
.bind(&chain_ids[..])
.bind(&addresses[..])
.bind(&pool_identifiers[..])
.bind(&dex_names[..])
.bind(&creation_blocks[..])
.bind(&token0_chains[..])
.bind(&token0_addresses[..])
.bind(&token1_chains[..])
.bind(&token1_addresses[..])
.bind(&fees[..])
.bind(&tick_spacings[..])
.bind(&initial_ticks[..])
.bind(&initial_sqrt_price_x96s[..])
.bind(&hook_addresses as &[Option<String>])
.execute(&self.pool)
.await
.map(|_| ())
.map_err(|e| anyhow::anyhow!("Failed to batch insert into pool table: {e}"))
}
/// Inserts multiple pool swaps in a single database operation using UNNEST for optimal performance.
///
/// # Errors
///
/// Returns an error if the database operation fails.
pub async fn add_pool_swaps_batch(
&self,
chain_id: u32,
swaps: &[PoolSwap],
) -> anyhow::Result<()> {
if swaps.is_empty() {
return Ok(());
}
// Prepare vectors for each column
let len = swaps.len();View on GitHub (pinned to 18893faf8b)
Solutions
- Check the wrapped `{e}`: 'unequal lengths' → align all bound slices; FK message → insert DEX rows first
- Assert every bound slice (addresses, dex ids, ticks, prices, hooks) has the same length before the call
- Run migrations so all pool columns exist; ensure parent DEX records are persisted before the batch
- Chunk very large batches (e.g. 1k-10k rows) to avoid oversized statements and to isolate failing rows
Example fix
// before let hooks: Vec<Option<String>> = pools.iter().filter_map(|p| p.hooks.map(|h| h.to_string())).collect(); // wrong length // after let hooks: Vec<Option<String>> = pools.iter().map(|p| p.hooks.map(|h| h.to_string())).collect(); // aligned assert_eq!(hooks.len(), pools.len());
Defensive patterns
Strategy: validation
Validate before calling
let n = pools.len();
anyhow::ensure!(addresses.len() == n && dex_ids.len() == n && initial_ticks.len() == n
&& initial_sqrt_price_x96s.len() == n && hook_addresses.len() == n, "UNNEST arrays misaligned");
anyhow::ensure!(!pools.is_empty(), "empty pool batch"); Type guard
fn batch_aligned(len: usize, slices: &[&[impl Sized]]) -> bool {
slices.iter().all(|s| s.len() == len)
} Try / catch
for chunk in pools.chunks(5_000) {
if let Err(e) = db.insert_pools_batch(chunk).await {
return Err(e.context(format!("pool batch insert failed at chunk starting index {}", chunk[0].index)));
}
} Prevention
- Derive every bound array from the same iteration over the input vec so lengths always match
- Insert parent DEX rows before the pool batch
- Chunk large batches to bound statement size and localize failures
- Keep schema migrations applied before bulk loads
When it happens
Trigger: Calling the batch pool insert with arrays of unequal length (Postgres 'UNNEST types/lengths mismatch'), an element in initial_sqrt_price_x96s or hook_addresses that fails the column cast, FK violation for pools whose DEX is missing, or execute failure on the pool connection.
Common situations: Bulk-loading pools from a snapshot where some pools' DEX rows were skipped; building the hook_addresses Option<String> vec at a different length after filtering; schema drift (missing hooks column in older DBs); oversized batch hitting statement size limits.
Related errors
- Failed to batch insert into pool_swap_event table: {e}
- Failed to batch insert into pool_liquidity_event table: {e}
- Failed to insert into block table: {e}
- Failed to batch insert into block table: {e}
- Failed to batch insert into pool_event_block table: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/cdfa1e229eecc756.
Report an issue: GitHub.