nautechsystems/nautilus_trader · error

Failed to commit transaction: {e}

Error message

Failed to commit transaction: {e}

What it means

Raised by `add_order_snapshot` when `transaction.commit()` fails after both the trader and order inserts succeeded. This means the transaction could not be finalized — typically because the connection was lost between the last statement and the commit — and PostgreSQL will roll the work back. No snapshot is persisted.

Source

Thrown at crates/infrastructure/src/sql/queries.rs:456

            .bind(snapshot.linked_order_ids.map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()))
            .bind(snapshot.parent_order_id.map(|x| x.to_string()))
            .bind(snapshot.exec_algorithm_id.map(|x| x.to_string()))
            .bind(snapshot.exec_algorithm_params.map(|x| serde_json::to_value(x).unwrap()))
            .bind(snapshot.exec_spawn_id.map(|x| x.to_string()))
            .bind(snapshot.tags.map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()))
            .bind(snapshot.init_id.to_string())
            .bind(snapshot.ts_init.to_string())
            .bind(snapshot.ts_last.to_string())
            .bind(snapshot.activation_price.map(|x| x.to_string()))
            .execute(&mut *transaction)
            .await
            .map(|_| ())
            .map_err(|e| anyhow::anyhow!("Failed to insert into order table: {e}"))?;

        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit transaction: {e}"))
    }

    /// Loads an `OrderSnapshot` entry by client order ID via the provided `pool`.
    ///
    /// # Errors
    ///
    /// Returns an error if the SQL SELECT or deserialization fails.
    pub async fn load_order_snapshot(
        pool: &PgPool,
        client_order_id: &ClientOrderId,
    ) -> anyhow::Result<Option<OrderSnapshot>> {
        sqlx::query_as::<_, OrderSnapshotRow>(r#"SELECT * FROM "order" WHERE client_order_id = $1"#)
            .bind(client_order_id.to_string())
            .fetch_optional(pool)
            .await
            .map(|row| row.map(|row| row.0))
            .map_err(|e| anyhow::anyhow!("Failed to load order snapshot: {e}"))
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Retry the entire `add_order_snapshot` call — the transaction is atomic, so a full retry is safe once connectivity is restored.
  2. Check database connectivity, failover status, and timeout settings (idle-in-transaction, statement_timeout).
  3. Increase pool `max_lifetime`/`idle_timeout` so connections are not recycled while a transaction is in flight.
  4. Inspect the wrapped `{e}` for server-side messages (e.g. serialization failure) and add retry-on-conflict logic if needed.

Example fix

// before: no retry, transient commit failures propagate
add_order_snapshot(&pool, &snapshot).await?;

// after: retry whole transactional write with backoff
let mut delay = Duration::from_millis(200);
while let Err(e) = add_order_snapshot(&pool, &snapshot).await {
    if !is_transient(&format!("{e:#}")) { return Err(e); }
    tokio::time::sleep(delay).await;
    delay *= 2;
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight connectivity check before starting transactional writes
sqlx::query("SELECT 1").execute(pool).await
    .map_err(|e| anyhow::anyhow!("database unreachable before snapshot write: {e}"))?;

Try / catch

let mut delay = std::time::Duration::from_millis(200);
loop {
    match add_order_snapshot(&pool, &snapshot).await {
        Ok(()) => break,
        Err(e) if is_transient(&format!("{e:#}")) => {
            tokio::time::sleep(delay).await;
            delay = (delay * 2).min(std::time::Duration::from_secs(5));
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Calling `add_order_snapshot` and having the connection drop, the statement timeout fire, or the server terminate the transaction between the final INSERT and `commit()`. Also occurs if the transaction was already aborted by a prior silent error or the pool connection was returned/closed prematurely.

Common situations: Network flakiness between app and database; long-running transaction hitting `idle_in_transaction_session_timeout`; Kubernetes pod eviction or DB failover mid-write; connection pool max-lifetime closing the connection under the transaction.

Related errors


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