mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
feat: expose Function refresh resource budgets in SDKs (#4951)
## Summary - Add the canonical Rust `FunctionWorkerResourceBudget` and `RefreshColumnOptions::resource_budget()` for explicit Enterprise Function refreshes. - Forward the nested budget through Python sync/async, PyO3, Rust remote requests, Node NAPI, and TypeScript. - Require positive CPU and memory values; normalize an omitted nested GPU count to numeric zero; reject the nested budget when combined with the top-level `gpu_count` shorthand. - Reject `resource_budget` on LanceDB Embedded refresh paths instead of silently ignoring it. - Document the Rust, Python, and TypeScript APIs, including that SQL servers currently require all three budget fields. The Rust, Python, and TypeScript additions use `cpu_millis` / `cpuMillis`, `memory_bytes` / `memoryBytes`, and `gpu_count` / `gpuCount`. The top-level GPU shorthand keeps its existing behavior and remains mutually exclusive with the complete nested budget. ## Scope and compatibility This change configures only the explicit refresh call. It does not configure `add_columns` automatic refresh and does not introduce a fleet-wide call-cap interface. A LanceDB Enterprise server must expose the nested `resource_budget` backfill contract. LanceDB Embedded continues to support its existing expression refresh defaults and rejects the new complete budget explicitly. ## Validation Validated against the final signed product source: - Rust: resource-budget remote serialization test `2 passed`; validation test `1 passed`. - Python: actual PyO3 build; remote-request matrix `22 passed`; invalid-option matrix `20 passed`; local rejection `22 passed`; Ruff check and format clean. - Repository-standard Pyright entry point: `0 errors`, within the repository's configured include set. - Node: native build and Biome lint passed; the existing remote-request test `1 passed`, including fractional, negative, overflow, NaN, and infinity nested GPU cases; the existing local-rejection test `1 passed`. - TypeDoc generation and generated-document cleanliness passed. - The native-tested product source differs from this head only in two semantically equivalent Node range checks required by Clippy. Current CI compiles and lints the final blobs. No dependency or lockfile changes are included.
This commit is contained in:
+53
-2
@@ -9,7 +9,8 @@ use lancedb::ipc::{ipc_file_to_batches, ipc_file_to_schema};
|
||||
use lancedb::table::{
|
||||
AddDataMode, ColumnAlteration as LanceColumnAlteration,
|
||||
FieldMetadataUpdate as LanceFieldMetadataUpdate, FtsToken as LanceDbFtsToken,
|
||||
NewColumnTransform, Ref, Table as LanceDbTable,
|
||||
FunctionWorkerResourceBudget as LanceFunctionWorkerResourceBudget, NewColumnTransform, Ref,
|
||||
Table as LanceDbTable,
|
||||
};
|
||||
use napi::Env;
|
||||
use napi::bindgen_prelude::*;
|
||||
@@ -1586,9 +1587,44 @@ pub struct FunctionErrorsOptions {
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
/// Aggregate worker resources for one Enterprise Function refresh.
|
||||
#[napi(object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionWorkerResourceBudget {
|
||||
/// Aggregate CPU budget in millicores; 1000 is one CPU.
|
||||
pub cpu_millis: f64,
|
||||
/// Aggregate memory budget in bytes.
|
||||
pub memory_bytes: f64,
|
||||
/// Aggregate GPU budget. Omitted means zero, a CPU-only refresh.
|
||||
pub gpu_count: Option<f64>,
|
||||
}
|
||||
|
||||
fn resource_budget_integer(value: f64, name: &str) -> napi::Result<u64> {
|
||||
const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0;
|
||||
if !value.is_finite() || value.fract() != 0.0 || !(1.0..=MAX_SAFE_INTEGER).contains(&value) {
|
||||
return Err(napi::Error::from_reason(format!(
|
||||
"{name} must be a positive safe integer"
|
||||
)));
|
||||
}
|
||||
Ok(value as u64)
|
||||
}
|
||||
|
||||
fn resource_budget_gpu_count(value: Option<f64>) -> napi::Result<u32> {
|
||||
let Some(value) = value else {
|
||||
return Ok(0);
|
||||
};
|
||||
if !value.is_finite() || value.fract() != 0.0 || !(0.0..=u32::MAX as f64).contains(&value) {
|
||||
return Err(napi::Error::from_reason(
|
||||
"resourceBudget.gpuCount must be an integer between 0 and 4294967295",
|
||||
));
|
||||
}
|
||||
Ok(value as u32)
|
||||
}
|
||||
|
||||
/// Options for `Table.refreshColumn` and `Table.refreshColumnAsync`. Every
|
||||
/// option is LanceDB Enterprise only; a LanceDB Embedded refresh ignores
|
||||
/// `batchSize`, `gpuClass` and `gpuCount` and rejects the rest.
|
||||
/// `batchSize`, `gpuClass` and `gpuCount` and rejects the rest, including
|
||||
/// `resourceBudget`.
|
||||
#[napi(object)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct RefreshColumnOptions {
|
||||
@@ -1599,6 +1635,9 @@ pub struct RefreshColumnOptions {
|
||||
pub batch_size: Option<u32>,
|
||||
/// GPU class for Function workers.
|
||||
pub gpu_class: Option<String>,
|
||||
/// Complete aggregate CPU and memory budget for a registered Function
|
||||
/// refresh. `gpuCount` inside the budget defaults to zero.
|
||||
pub resource_budget: Option<FunctionWorkerResourceBudget>,
|
||||
/// Positive aggregate GPU budget for Function workers.
|
||||
pub gpu_count: Option<u32>,
|
||||
/// `"fail"` (default) stops on a row error; `"skip"` writes null and
|
||||
@@ -1628,6 +1667,18 @@ impl TryFrom<RefreshColumnOptions> for lancedb::table::RefreshColumnOptions {
|
||||
options.where_clause = value.where_clause;
|
||||
options.batch_size = value.batch_size.map(|size| size as usize);
|
||||
options.gpu_class = value.gpu_class;
|
||||
options.resource_budget = value
|
||||
.resource_budget
|
||||
.map(|budget| {
|
||||
Ok::<_, napi::Error>(
|
||||
LanceFunctionWorkerResourceBudget::new(
|
||||
resource_budget_integer(budget.cpu_millis, "resourceBudget.cpuMillis")?,
|
||||
resource_budget_integer(budget.memory_bytes, "resourceBudget.memoryBytes")?,
|
||||
)
|
||||
.with_gpu_count(resource_budget_gpu_count(budget.gpu_count)?),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
options.gpu_count = value.gpu_count;
|
||||
options.error_policy = match value.error_policy.as_deref() {
|
||||
None => None,
|
||||
|
||||
Reference in New Issue
Block a user