Skip to main content

DataFusion Session

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

Dependencies

Use the DataFusion version pinned by Algeon:

[dependencies]
datafusion = "55.0"
algeon-datafusion = { path = "../algeon/crates/algeon-datafusion", features = ["cugraph"] }
futures = "0.3"

Algeon is currently consumed from the algeon-datafusion crate in the repository. Clone the repository recursively as described in Building from Source, then point the application at crates/algeon-datafusion. Its component crates are resolved through the root workspace.

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

Build one backend

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

use datafusion::execution::SessionStateBuilder;
use datafusion::prelude::SessionContext;
use algeon_datafusion::backend::{
GpuMemoryOwnership, AlgeonGpuBackend, AlgeonGpuDeviceConfig, AlgeonGpuDeviceProfile,
};

let device = AlgeonGpuDeviceConfig::new(AlgeonGpuDeviceProfile::new(0), GpuMemoryOwnership::WholeDeviceExclusive);
let backend = AlgeonGpuBackend::builder()
.devices([device])
.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. AlgeonGpuDeviceProfile::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 and cuVS SQL

install_on installs backend-bound native rules and coverage. It does not install cuGraph SQL or cuVS SQL. To enable the cugraph_* table functions on the session, chain try_with_cugraph_sql(config):

use algeon_datafusion::{
cugraph_sql::CugraphSqlConfig,
session::AlgeonSessionStateBuilderExt,
};

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

let ctx = SessionContext::new_with_state(state);

cuGraph SQL functions resolve the backend's admission and native execution configuration from session state when executed. To add the cuvs_* functions, chain try_with_cuvs_sql() in the same manner; it takes no configuration. The functions execute only when the crate was built with the cuvs feature. See cuVS execution.

Execute one attempt per query

For application-owned endpoints, create an attempt before planning, take its completion receiver before running the query, and read the report whether or not the query succeeded. The snippet assumes an orders table is already registered in ctx, for example with CREATE EXTERNAL TABLE ... STORED AS PARQUET or register_parquet:

use algeon_datafusion::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 completion = attempt.completion();

let result = async {
let stream = attempt.sql(&ctx, "SELECT SUM(amount) FROM orders").await?;
stream.try_collect::<Vec<_>>().await
}
.await;

let report: Arc<QueryAttemptReport> = completion.await;
let batches = result?;

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. Taking the receiver before planning and awaiting it after the result is what keeps the report available on the error path; returning early with ? on the stream would drop the attempt and route the report to the abandoned-report queue.

Frameworks may own this boundary for you. The Algeon 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 AlgeonGpuQueuePolicy. This fragment replaces the backend construction shown above:

use algeon_datafusion::backend::{
GpuDeviceMemoryLimit, GpuMemoryOwnership, AlgeonGpuBackend, AlgeonGpuDeviceConfig,
AlgeonGpuDeviceProfile, AlgeonGpuQueuePolicy,
};

let device = AlgeonGpuDeviceConfig::new(
AlgeonGpuDeviceProfile::new(0)
.with_memory_limit(GpuDeviceMemoryLimit::Explicit {
limit_bytes: 8 * 1024 * 1024 * 1024,
})
.with_backend_reserve_bytes(512 * 1024 * 1024)
.with_default_query_limit_bytes(512 * 1024 * 1024)
.with_max_active_attempts(10),
GpuMemoryOwnership::WholeDeviceExclusive,
);
let backend = AlgeonGpuBackend::builder()
.devices([device])
.queue_policy(
AlgeonGpuQueuePolicy::default()
.with_max_queued_attempts(64)
.with_max_queue_wait(std::time::Duration::from_secs(30))
)
.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. Eligible waiters are served in FIFO order; there is no overtaking policy.

Native execution policy is owned solely by the backend builder. Configure native execution with with_native_execution_config(...) on AlgeonGpuBackend::builder() before build().

DataFusion sessions carry standard DataFusion configuration only; native execution policy and graph input bounds cannot be altered via SQL SET or session extensions.

Choose an execution mode

Set AlgeonGpuExecutionMode on the shared backend before installing sessions:

Rust modeBehaviorUse it when
NativePreferred (default)Select supported relational SQL for native GPU execution; retain DataFusion for unsupported or unselected candidates. Explicit GPU functions still run on GPU.You want GPU acceleration for ordinary SQL as well as graph and vector functions.
FunctionsOnlyKeep ordinary relational SQL on DataFusion CPU; run explicit cugraph_* and cuvs_* functions on GPU.You want DataFusion to scan, join, filter, and aggregate around GPU algorithms.
NativeRequiredAttempt native relational selection, then reject any completed plan containing DataFusion CPU execution.You need to enforce a plan with no DataFusion CPU operators.

This replaces the backend construction above, using the same device:

use algeon_datafusion::planner::AlgeonGpuExecutionMode;

let backend = AlgeonGpuBackend::builder()
.with_execution_mode(AlgeonGpuExecutionMode::FunctionsOnly)
.devices([device])
.build()?;

Install the required function families with the features and session methods shown above. FunctionsOnly changes relational planning; it does not register functions or remove their GPU runtime and backend requirements. The Flight SQL equivalents are documented in Configuration.

Compose CPU and GPU work

In FunctionsOnly, a CPU relation can feed a GPU function whose Arrow results return to CPU SQL. A GPU result can also pass through CPU SQL into another GPU function. cuGraph inputs remain registered table or view names; cuVS inputs remain parenthesized subqueries. Supported compositions share the same backend admission and immutable query grant.

CPU-to-GPU input boundaries stage Arrow data in host memory before upload. The current staging path does not spill: cuGraph host input and output staging each default to a 1 GiB ceiling; cuVS host input staging has a cumulative 1 GiB ceiling across one operation's inputs. DataFusion's memory pool can impose a lower limit. These are host staging limits, separate from the GPU query cap; keep intermediate inputs bounded. See the runnable execution modes example for PageRank with CPU filtering and ordering.

DataFusion fallback and GPU-only plans

An explicit GPU function has no CPU algorithm fallback. Unsupported function inputs, memory exhaustion, cancellation, and runtime failures remain errors; after GPU execution starts, Algeon never replays the query on CPU. See Handling Errors for the structured error contract. NativeRequired checks the completed plan without replanning under a different policy; it does not make unsupported operations GPU-capable.

Use GPU Coverage Validation to inspect the exact final disposition before execution. validate_query(&ctx, sql) is the Rust entry point; algeon_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 algeon_datafusion::{
planner::AlgeonNativeOptimizerConfig,
session::AlgeonSessionStateBuilderExt,
};

let state = SessionStateBuilder::new_with_default_features()
.with_algeon_native(AlgeonNativeOptimizerConfig::default())
.build();

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