nautechsystems/nautilus_trader · error · anyhow::Error

Failed to write pool data: {e}

Error message

Failed to write pool data: {e}

What it means

write_pool_binary serializes a single pool row in PostgreSQL COPY binary format and sends it through the copy_in sink; failures are wrapped in this anyhow error. NULL fields are encoded as -1 i32 length prefixes, so errors here typically indicate sink/connection issues rather than value problems.

Source

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

        if let Some(ref initial_sqrt_price) = pool.initial_sqrt_price_x96 {
            write_copy_numeric(&mut row_data, initial_sqrt_price);
        } else {
            row_data.write_all(&(-1i32).to_be_bytes())?; // NULL
        }

        if let Some(ref hooks) = pool.hooks {
            let hooks_bytes = hooks.to_string().as_bytes().to_vec();
            row_data.write_all(&(hooks_bytes.len() as i32).to_be_bytes())?;
            row_data.write_all(&hooks_bytes)?;
        } else {
            row_data.write_all(&(-1i32).to_be_bytes())?; // NULL
        }

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

    /// Writes the PostgreSQL COPY binary format trailer.
    ///
    /// The trailer is a 2-byte value of -1 to indicate end of data.
    async fn write_copy_trailer(
        &self,
        copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
    ) -> anyhow::Result<()> {
        // Binary trailer: -1 as i16 to indicate end of data
        let trailer = (-1i16).to_be_bytes();
        copy_in
            .send(trailer.to_vec())
            .await
            .map_err(|e| anyhow::anyhow!("Failed to write COPY trailer: {e}"))?;
        Ok(())
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the wrapped source error to distinguish connectivity from encoding problems
  2. Confirm the pools table schema still matches write_pool_binary's field order and types
  3. Keep the connection/pool alive for the duration of the COPY (no early drops, adequate timeouts)
  4. Re-run copy_pools; the aborted COPY is rolled back so retrying is safe

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure pool table exists with expected columns before copy
sqlx::query("SELECT 1 FROM information_schema.tables WHERE table_name='pool'")
    .fetch_optional(&pool).await?
    .ok_or_else(|| anyhow::anyhow!("pool table missing; run migrations"))?;

Try / catch

async fn copy_pools_guarded(db: &CacheDatabase, chain: &Chain, pools: &[Pool]) -> anyhow::Result<()> {
    db.copy_pools(chain, pools).await.map_err(|e| {
        anyhow::anyhow!("copy_pools failed (partitions created? schema current?): {e:#}")
    })
}

Prevention

When it happens

Trigger: copy_in.send(row_data) fails during copy_pools — the PostgreSQL connection dropped, the server aborted the COPY stream, or the row bytes no longer match the pool table's binary column layout.

Common situations: Long-running pool cache sync killed by network timeout; DDL change on the pool table invalidating the hard-coded binary field order; database connection pool closed by the application early.

Related errors


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