nautechsystems/nautilus_trader · error · anyhow::Error

Failed to write pool swap data: {e}

Error message

Failed to write pool swap data: {e}

What it means

write_pool_swap_binary sends one serialized PoolSwap row over the COPY BINARY stream; failure from PgCopyIn::send is wrapped in this error and aborts the whole copy_pool_swaps batch. Indicates the connection dropped mid-copy or the server already aborted the COPY session (a prior row failed).

Source

Thrown at crates/adapters/blockchain/src/cache/copy.rs:538

            row_data.write_all(spot_price_bytes)?;

            let exec_price_decimal = trade_info.execution_price.as_decimal();
            let exec_price_str = exec_price_decimal.to_string();
            let exec_price_bytes = exec_price_str.as_bytes();
            row_data.write_all(&(exec_price_bytes.len() as i32).to_be_bytes())?;
            row_data.write_all(exec_price_bytes)?;
        } else {
            row_data.write_all(&(-1i32).to_be_bytes())?;
            row_data.write_all(&(-1i32).to_be_bytes())?;
            row_data.write_all(&(-1i32).to_be_bytes())?;
            row_data.write_all(&(-1i32).to_be_bytes())?;
            row_data.write_all(&(-1i32).to_be_bytes())?;
        }

        copy_in
            .send(row_data)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to write pool swap data: {e}"))?;
        Ok(())
    }

    /// Writes a single pool liquidity update in PostgreSQL binary format.
    ///
    /// Each row in binary format consists of:
    /// - 2-byte field count
    /// - For each field: 4-byte length followed by data (or -1 for NULL)
    async fn write_pool_liquidity_update_binary(
        &self,
        copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
        chain_id: u32,
        update: &PoolLiquidityUpdate,
    ) -> anyhow::Result<()> {
        use std::io::Write;
        let mut row_data = Vec::new();

        row_data.write_all(&15u16.to_be_bytes())?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the wrapped sqlx error detail for the root cause (transport vs server abort)
  2. Validate that all referenced pools/tokens exist before copying swaps
  3. Re-verify write_pool_swap_binary's field order/count against the COPY statement
  4. Chunk the swaps list and retry to narrow down the failing row

Example fix

// before
handler.copy_pool_swaps(chain_id, &swaps).await?;
// after
for chunk in swaps.chunks(1000) {
    handler.copy_pool_swaps(chain_id, chunk).await?;
}
Defensive patterns

Strategy: retry

Try / catch

if let Err(e) = handler.copy_pool_swaps(chain_id, &swaps).await {
    if e.to_string().contains("Failed to write pool swap data") {
        tokio::time::sleep(Duration::from_secs(5)).await;
        return handler.copy_pool_swaps(chain_id, &swaps).await; // COPY is atomic, safe to retry batch
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Connection reset during a large swap batch, server-side abort propagating to the next send (constraint failure on an earlier swap), mismatch between serialized fields and the pool_swap COPY column list.

Common situations: High-volume swap backfills over flaky networks; swaps referencing uncached pools/tokens causing earlier-row failures that surface here; schema drift after adding a column to pool_swap.

Related errors


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