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
- Check the wrapped sqlx error detail for the root cause (transport vs server abort)
- Validate that all referenced pools/tokens exist before copying swaps
- Re-verify write_pool_swap_binary's field order/count against the COPY statement
- 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
- Ensure pools/tokens exist before swaps to avoid server-side aborts
- Chunk large swap lists
- Keep the serializer in sync with the COPY column list
- Add connection health checks to PgPoolOptions
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
- Failed to write block data: {e}
- Failed to write pool liquidity update data: {e}
- Failed to write COPY header: {e}
- Failed to load active execution intent: {e}
- Failed to start replacement transaction persistence: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/fff9d398655a4ab5.
Report an issue: GitHub.