nautechsystems/nautilus_trader · error
Failed to start COPY operation: {e}
Error message
Failed to start COPY operation: {e} What it means
The blockchain cache inserts blocks using PostgreSQL `COPY ... FROM STDIN (FORMAT BINARY)` for bulk performance. This error wraps a failure of `copy_in_raw` — i.e. the COPY statement itself failed to start against the pool (connection issues, SQL error, or permissions). No block data has been written at this stage.
Source
Thrown at crates/adapters/blockchain/src/cache/copy.rs:65
///
/// Returns an error if the COPY operation fails.
pub async fn copy_blocks(&self, chain_id: u32, blocks: &[Block]) -> anyhow::Result<()> {
if blocks.is_empty() {
return Ok(());
}
let copy_statement = "
COPY block (
chain_id, number, hash, parent_hash, miner, gas_limit, gas_used, timestamp,
base_fee_per_gas, blob_gas_used, excess_blob_gas,
l1_gas_price, l1_gas_used, l1_fee_scalar
) FROM STDIN WITH (FORMAT BINARY)";
let mut copy_in = self
.pool
.copy_in_raw(copy_statement)
.await
.map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
// Write binary header
self.write_copy_header(&mut copy_in).await?;
// Write each block as binary data
for block in blocks {
self.write_block_binary(&mut copy_in, chain_id, block)
.await?;
}
// Write binary trailer
self.write_copy_trailer(&mut copy_in).await?;
// Finish the COPY operation
copy_in
.finish()
.await
.map_err(|e| anyhow::anyhow!("Failed to finish COPY operation: {e}"))?;View on GitHub (pinned to 18893faf8b)
Solutions
- Read the wrapped `e` message: fix the underlying cause (connection refused, auth failed, undefined table, etc.).
- Verify database connectivity (`psql $DATABASE_URL`) and that schema migrations have created the target table.
- Check the pool is healthy and connection limits/SSL settings are correct for the environment.
- Retry the operation; for transient failures, add reconnect/backoff around `copy_blocks`.
Defensive patterns
Strategy: retry
Validate before calling
let conn_ok = sqlx::query("SELECT 1").execute(&pool).await.is_ok();
if !conn_ok { return Err(anyhow::anyhow!("database unreachable before COPY")); } Try / catch
match copy_blocks(&cache, &blocks).await {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("Failed to start COPY") => {
tokio::time::sleep(Duration::from_secs(2)).await;
retry_with_backoff(|| copy_blocks(&cache, &blocks), 3).await
}
Err(e) => Err(e),
} Prevention
- Run migrations before starting cache ingestion.
- Health-check the pool (SELECT 1) before bulk operations.
- Set sane pool idle timeouts so connections aren't dead on reuse.
- Grant the DB user INSERT on target tables.
When it happens
Trigger: `copy_blocks` at crates/adapters/blockchain/src/cache/copy.rs:65 calls `pool.copy_in_raw(copy_statement)` and the underlying tokio-postgres/tls layer returns an error — dead pooled connection, unreachable database, invalid credentials, or the target table missing.
Common situations: Database restarted or network blip while a pooled connection sits idle; wrong DSN/credentials in cache config; migrations not applied so the blocks table doesn't exist; user lacking COPY/INSERT privilege.
Related errors
- Failed to finish COPY operation: {e}
- Failed to write pool fee collect data: {e}
- Failed to write token data: {e}
- Failed to write pool data: {e}
- Failed to write COPY trailer: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2576fc4cec37404b.
Report an issue: GitHub.