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

  1. Read the wrapped `e` message: fix the underlying cause (connection refused, auth failed, undefined table, etc.).
  2. Verify database connectivity (`psql $DATABASE_URL`) and that schema migrations have created the target table.
  3. Check the pool is healthy and connection limits/SSL settings are correct for the environment.
  4. 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

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


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