Skip to main content

DataFusion Session

Use this integration when your Rust service already owns request handling, authentication, catalogs, and SessionContext lifecycle. Nexus adds native planning and GPU execution without taking over those application concerns.

Dependencies

Use the DataFusion version pinned by Nexus:

[dependencies]
datafusion = "54.1"
datafusion-nexus = { path = "../datafusion-nexus", features = ["cugraph"] }
futures = "0.3"

Nexus is currently consumed from the root source checkout. Clone the repository recursively as described in Building from Source, then point the application at the root package. The component crates are resolved through the root workspace.

The root crate has no default features. Add cugraph for graph SQL, iceberg for Iceberg sources, cuvs for cuVS bindings, and nvml for optional device diagnostics. Omit all of them for relational cuDF execution only.

Build one backend

NexusGpuBackend is the process-scoped owner of CUDA devices, admission, per-device ledgers, and execution resources:

use datafusion::execution::SessionStateBuilder;
use datafusion::prelude::SessionContext;
use datafusion_nexus::backend::{
GpuMemoryOwnership, NexusGpuBackend, NexusGpuDeviceProfile,
};

let backend = NexusGpuBackend::builder()
.device_profiles([NexusGpuDeviceProfile::new(0)])
.device_memory_ownership([(0, GpuMemoryOwnership::WholeDeviceExclusive)])
.build()?;

let state = backend
.install_on(SessionStateBuilder::new_with_default_features())?
.build();

let ctx = SessionContext::new_with_state(state);

Every selected ordinal needs one profile and one matching ownership declaration. NexusGpuDeviceProfile::new(0) uses automatic capacity and one active attempt; add explicit limits only when your deployment requires them.

Only one live backend may exist in a process. Clone and share this backend across every session and frontend instead of building another one.

Add cuGraph SQL

Install cuGraph through the backend so the functions inherit the same admission owner:

use datafusion_nexus::cugraph_sql::CugraphSqlConfig;

let state = backend
.install_on_with_cugraph_sql(
SessionStateBuilder::new_with_default_features(),
CugraphSqlConfig::default(),
)?
.build();

let ctx = SessionContext::new_with_state(state);

The equivalent manual order is backend.install_on(builder)? followed by with_cugraph_sql(...). Installing cuGraph first, or registering its table functions directly on an already-built context, bypasses backend admission and is rejected or unsafe for a backend-managed process.

install_on also installs Nexus native rules, GPU coverage, the common GPU function catalog, and the relation-aware cuVS planner.

Current cuVS boundary

The cuvs feature supplies native bindings, but backend-managed bounded cuVS execution still returns structured unsupported_operator because cuVS does not yet provide a bounded peak-memory preflight. Nexus does not use a CPU fallback or an unadmitted GPU path. See cuVS execution availability.

Execute one attempt per query

For application-owned endpoints, create an attempt before planning and retain its completion report:

use datafusion_nexus::attempt::{AttemptOptions, QueryAttemptReport};
use futures::TryStreamExt;
use std::sync::Arc;

let attempt = backend.begin_attempt(AttemptOptions {
query_id: Some("request-42".to_owned()),
..Default::default()
})?;

let stream = attempt
.sql(&ctx, "SELECT SUM(amount) FROM orders")
.await?;
let batches = stream.try_collect::<Vec<_>>().await?;
let report: Arc<QueryAttemptReport> = attempt.completion().await;

The attempt clones the session into invocation-local planning state. Its report records the final disposition, admission grant, timing, and terminal outcome. Completion seals on stream EOF, error, cancellation, or drop, so keep the completion handle even when execution fails.

Frameworks may own this boundary for you. The Nexus Flight SQL server, for example, begins the attempt while issuing a statement ticket and carries it through DoGet. Call begin_attempt directly when your application owns the query invocation.

Admission configuration

Device capacity belongs to profiles; shared queue behavior belongs to NexusGpuQueuePolicy:

use datafusion_nexus::backend::NexusGpuQueuePolicy;

let backend = NexusGpuBackend::builder()
.device_profiles([
NexusGpuDeviceProfile::new(0)
.with_max_active_attempts(10)
.with_attempt_floor_bytes(Some(512 * 1024 * 1024)),
])
.device_memory_ownership([(0, GpuMemoryOwnership::WholeDeviceExclusive)])
.queue_policy(
NexusGpuQueuePolicy::default()
.with_max_queued_attempts(64)
.with_max_queue_wait(std::time::Duration::from_secs(30))
.with_max_overtakes_per_waiter(4),
)
.build()?;

Planning does not acquire a GPU grant. Admission begins when the first native stream is polled, and all native nodes in that attempt share the selected device and immutable grant. A full queue or expired wait returns structured service_overloaded backpressure.

Backend-installed sessions freeze datafusion_nexus.native.* options because the shared backend owns that policy. Configure native execution with with_native_execution_config(...) before build().

The planner-evidence key datafusion_nexus.request.graph_raw_input_edges remains mutable and accepts a positive edge count or none. Set it on a request-local context before planning; do not mutate a shared session concurrently. The complete server/session key mapping is in Configuration.

DataFusion fallback and GPU-only plans

By default, an unsupported or unselected whole relational candidate keeps its executable DataFusion plan. Add with_no_datafusion_cpu_requirement() to the backend builder to reject a completed plan that still contains DataFusion CPU execution. Nexus does not replan under a different policy, and a native runtime failure is never retried on CPU.

Use GPU Coverage Validation to inspect the exact final disposition before execution. validate_query(&ctx, sql) is the Rust entry point; nexus_explain_coverage(...) exposes the same planning evidence through SQL.

Observe and shut down

Per-attempt evidence comes from QueryAttemptReport. The backend exposes service-level admission_snapshot, device_ledger_reports, device_memory_failure_reports, and backend_metrics_snapshot methods. At shutdown, stop accepting work, drain streams and sessions, then call close_and_wait on the shared backend.

See the runnable attempt lifecycle example for the complete per-query path, or the Embedded Backend Example for one backend shared by REST and Flight SQL.

Direct optimizer install

The raw session extension is useful for planning and coverage tests:

use datafusion::execution::SessionStateBuilder;
use datafusion_nexus::{
planner::NexusNativeOptimizerConfig,
session::NexusSessionStateBuilderExt,
};

let state = SessionStateBuilder::new_with_default_features()
.with_nexus_native(NexusNativeOptimizerConfig::default())
.build();

This path has no admission owner. Native execution fails locally with gpu_backend_required, so production GPU execution should always install a NexusGpuBackend.