use std::net::SocketAddr;
use std::sync::Arc;
use ballista_core::extension::{
SessionConfigExt, ballista_aggregate_functions, ballista_scalar_functions,
ballista_window_functions,
};
use ballista_core::registry::BallistaFunctionRegistry;
use ballista_core::serde::protobuf::scheduler_grpc_client::SchedulerGrpcClient;
use ballista_core::serde::{
BallistaCodec, BallistaLogicalExtensionCodec, BallistaPhysicalExtensionCodec,
};
use ballista_core::{ConfigProducer, RuntimeProducer};
use ballista_executor::executor_process::{ExecutorProcessConfig, start_executor_process};
use ballista_executor::new_standalone_executor_from_builder;
use ballista_scheduler::cluster::BallistaCluster;
use ballista_scheduler::config::SchedulerConfig;
use ballista_scheduler::scheduler_process::start_server;
use ballista_scheduler::scheduler_server::SessionBuilder;
use ballista_scheduler::standalone::new_standalone_scheduler_with_builder;
use datafusion::execution::SessionStateBuilder;
use datafusion::execution::runtime_env::RuntimeEnvBuilder;
use datafusion::execution::session_state::SessionState;
use datafusion::prelude::SessionConfig;
use datafusion_proto::physical_plan::PhysicalExtensionCodec;
use oxidelake_compute::oxide_udfs;
use oxidelake_core::{BackendKind, EngineError};
use oxidelake_planner::{HardwarePlacementRule, OxidePhysicalCodec, physical_optimizer_rules};
use oxidelake_storage::{with_gpu_batch_size, with_pruning};
fn ballista_error(err: impl std::fmt::Display) -> EngineError {
EngineError::execution(format!("ballista: {err}"))
}
pub fn oxide_codec() -> Arc<dyn PhysicalExtensionCodec> {
Arc::new(OxidePhysicalCodec::new(Arc::new(
BallistaPhysicalExtensionCodec::default(),
)))
}
pub fn ballista_codec() -> BallistaCodec {
BallistaCodec::new(
Arc::new(BallistaLogicalExtensionCodec::default()),
oxide_codec(),
)
}
pub fn session_config() -> SessionConfig {
with_pruning(SessionConfig::new_with_ballista())
.with_ballista_physical_extension_codec(oxide_codec())
}
pub fn config_producer() -> ConfigProducer {
Arc::new(session_config)
}
pub fn build_session_state(
config: SessionConfig,
target: BackendKind,
) -> datafusion::error::Result<SessionState> {
let mut scalar_functions = ballista_scalar_functions();
scalar_functions.extend(oxide_udfs());
let config = if target.is_gpu() {
with_gpu_batch_size(config)
} else {
config
};
Ok(SessionStateBuilder::new()
.with_default_features()
.with_config(config)
.with_runtime_env(Arc::new(RuntimeEnvBuilder::new().build()?))
.with_scalar_functions(scalar_functions)
.with_aggregate_functions(ballista_aggregate_functions())
.with_window_functions(ballista_window_functions())
.with_physical_optimizer_rules(physical_optimizer_rules(HardwarePlacementRule::new(target)))
.build())
}
pub fn session_builder(target: BackendKind) -> SessionBuilder {
Arc::new(move |config: SessionConfig| build_session_state(config, target))
}
pub fn runtime_producer() -> RuntimeProducer {
Arc::new(|_config: &SessionConfig| Ok(Arc::new(RuntimeEnvBuilder::new().build()?)))
}
pub fn function_registry() -> BallistaFunctionRegistry {
let mut registry = BallistaFunctionRegistry::default();
for udf in oxide_udfs() {
registry.scalar_functions.insert(udf.name().to_owned(), udf);
}
registry
}
pub fn scheduler_config(bind_host: &str, port: u16, target: BackendKind) -> SchedulerConfig {
SchedulerConfig {
bind_host: bind_host.to_owned(),
bind_port: port,
override_session_builder: Some(session_builder(target)),
override_config_producer: Some(config_producer()),
override_physical_codec: Some(oxide_codec()),
..SchedulerConfig::default()
}
}
pub async fn run_scheduler(config: SchedulerConfig) -> Result<(), EngineError> {
let address: SocketAddr = format!("{}:{}", config.bind_host, config.bind_port)
.parse()
.map_err(|e| EngineError::plan(format!("invalid scheduler bind address: {e}")))?;
let config = Arc::new(config);
let cluster = BallistaCluster::new_from_config(&config)
.await
.map_err(ballista_error)?;
start_server(cluster, address, config)
.await
.map_err(ballista_error)
}
pub fn executor_config(
scheduler_host: &str,
scheduler_port: u16,
port: u16,
grpc_port: u16,
concurrent_tasks: Option<usize>,
work_dir: Option<String>,
) -> ExecutorProcessConfig {
let defaults = ExecutorProcessConfig::default();
ExecutorProcessConfig {
scheduler_host: scheduler_host.to_owned(),
scheduler_port,
port,
grpc_port,
concurrent_tasks: concurrent_tasks.unwrap_or(defaults.concurrent_tasks),
work_dir,
override_physical_codec: Some(oxide_codec()),
override_function_registry: Some(Arc::new(function_registry())),
override_config_producer: Some(config_producer()),
..defaults
}
}
pub async fn run_executor(config: ExecutorProcessConfig) -> Result<(), EngineError> {
start_executor_process(Arc::new(config))
.await
.map_err(ballista_error)
}
pub async fn start_standalone(
target: BackendKind,
executors: usize,
concurrent_tasks: usize,
) -> Result<SocketAddr, EngineError> {
let addr = new_standalone_scheduler_with_builder(
session_builder(target),
config_producer(),
ballista_codec(),
)
.await
.map_err(ballista_error)?;
for _ in 0..executors.max(1) {
let client = SchedulerGrpcClient::connect(format!("http://{addr}"))
.await
.map_err(ballista_error)?;
new_standalone_executor_from_builder(
client,
concurrent_tasks.max(1),
config_producer(),
runtime_producer(),
ballista_codec(),
function_registry(),
)
.await
.map_err(ballista_error)?;
}
tracing::info!(%addr, executors, "standalone Ballista cluster started");
Ok(addr)
}