mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
feat: expose Function call concurrency limits (#4956)
## Summary Adds an optional `max_concurrency` setting to Function registration and explicit refresh: - A registered Function version can define the default maximum number of user-code calls in flight for one logical execution. - A refresh can override that default for that execution only. - The value must be a positive `u32` and is independent from `resource_budget`; it is not an account-wide quota. - Omitting the option preserves the previous request payload. Local embedded refreshes reject this Enterprise-only option. Rust, Python sync/async, PyO3, and the existing Node refresh surface forward the option. Node has no Function registration surface today, so no registration API is added there. The generated TypeDoc pages are included for the new public option. ## Validation The native-tested source covers Function version decoding, registration validation and serialization, the Flight SQL registration ticket and returned version, remote refresh forwarding, local rejection, and Node's numeric boundary before conversion. Exact stage results are Rust `1+3` plus four remote cases at `1` each; Python `14/1/49/24`; Node `57` remote and `1` local rejection. Ruff, standard repository Pyright, and Biome passed. The tested Python extension was `21aa256a970f4a16ea8253d68401d0fc573d946412e8752155dfcad63f30654d`; the tested Node addon and generated native declaration were `f6089f861981f96f8ce0f6dbef43de0907b9eeb4b63e84cdd5da14e00d6cbd30` and `4c4ceb205824075d706897c726c215dcf839880c757f0d0582d24cec5f938416`. The final 24-file diff preserves the 22 tested product/test/fixture/source-doc blobs and adds only two generated TypeDoc pages.
This commit is contained in:
+34
-2
@@ -48,6 +48,7 @@ mod scannable;
|
||||
struct RefreshColumnArgs {
|
||||
where_clause: Option<String>,
|
||||
batch_size: Option<i64>,
|
||||
max_concurrency: Option<u32>,
|
||||
publication_interval_rows: Option<i64>,
|
||||
resource_budget: Option<FunctionWorkerResourceBudget>,
|
||||
gpu_class: Option<String>,
|
||||
@@ -73,6 +74,31 @@ fn positive_count<T: TryFrom<i64>>(value: Option<i64>, name: &str) -> PyResult<O
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn positive_u32_integer(value: Option<&Bound<'_, PyAny>>, name: &str) -> PyResult<Option<u32>> {
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
if value.is_none() || value.is_instance_of::<PyBool>() {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"{name} must be an integer between 1 and 4294967295"
|
||||
)));
|
||||
}
|
||||
let parsed = value.extract::<i64>().map_err(|_| {
|
||||
PyValueError::new_err(format!(
|
||||
"{name} must be an integer between 1 and 4294967295"
|
||||
))
|
||||
})?;
|
||||
u32::try_from(parsed)
|
||||
.ok()
|
||||
.filter(|parsed| *parsed > 0)
|
||||
.map(Some)
|
||||
.ok_or_else(|| {
|
||||
PyValueError::new_err(format!(
|
||||
"{name} must be an integer between 1 and 4294967295"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn resource_budget_integer(
|
||||
value: &Bound<'_, PyAny>,
|
||||
name: &str,
|
||||
@@ -136,6 +162,7 @@ fn refresh_column_options(args: RefreshColumnArgs) -> PyResult<RefreshColumnOpti
|
||||
let RefreshColumnArgs {
|
||||
where_clause,
|
||||
batch_size,
|
||||
max_concurrency,
|
||||
publication_interval_rows,
|
||||
resource_budget,
|
||||
gpu_class,
|
||||
@@ -179,6 +206,7 @@ fn refresh_column_options(args: RefreshColumnArgs) -> PyResult<RefreshColumnOpti
|
||||
.map(usize::try_from)
|
||||
.transpose()
|
||||
.map_err(|_| PyValueError::new_err("batch_size must be greater than zero"))?;
|
||||
options.max_concurrency = max_concurrency;
|
||||
options.publication_interval_rows =
|
||||
positive_count(publication_interval_rows, "publication_interval_rows")?;
|
||||
options.resource_budget = resource_budget;
|
||||
@@ -2282,13 +2310,14 @@ impl Table {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (column, *, r#where=None, batch_size=None, publication_interval_rows=None, resource_budget=None, gpu_class=None, gpu_count=None, retry_policy=None, error_policy=None, max_failed_fraction=None, max_failed_rows=None, row_timeout_seconds=None, init_timeout_seconds=None, num_workers=None, max_executor_processes_per_worker=None))]
|
||||
#[pyo3(signature = (column, *, r#where=None, batch_size=None, max_concurrency=None, publication_interval_rows=None, resource_budget=None, gpu_class=None, gpu_count=None, retry_policy=None, error_policy=None, max_failed_fraction=None, max_failed_rows=None, row_timeout_seconds=None, init_timeout_seconds=None, num_workers=None, max_executor_processes_per_worker=None))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn refresh_column<'py>(
|
||||
self_: PyRef<'py, Self>,
|
||||
column: String,
|
||||
r#where: Option<String>,
|
||||
batch_size: Option<i64>,
|
||||
max_concurrency: Option<Bound<'py, PyAny>>,
|
||||
publication_interval_rows: Option<i64>,
|
||||
resource_budget: Option<Bound<'py, PyDict>>,
|
||||
gpu_class: Option<String>,
|
||||
@@ -2305,6 +2334,7 @@ impl Table {
|
||||
let options = refresh_column_options(RefreshColumnArgs {
|
||||
where_clause: r#where,
|
||||
batch_size,
|
||||
max_concurrency: positive_u32_integer(max_concurrency.as_ref(), "max_concurrency")?,
|
||||
publication_interval_rows,
|
||||
resource_budget: parse_resource_budget(resource_budget.as_ref())?,
|
||||
gpu_class,
|
||||
@@ -2328,13 +2358,14 @@ impl Table {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (column, *, r#where=None, batch_size=None, publication_interval_rows=None, resource_budget=None, gpu_class=None, gpu_count=None, retry_policy=None, error_policy=None, max_failed_fraction=None, max_failed_rows=None, row_timeout_seconds=None, init_timeout_seconds=None, num_workers=None, max_executor_processes_per_worker=None))]
|
||||
#[pyo3(signature = (column, *, r#where=None, batch_size=None, max_concurrency=None, publication_interval_rows=None, resource_budget=None, gpu_class=None, gpu_count=None, retry_policy=None, error_policy=None, max_failed_fraction=None, max_failed_rows=None, row_timeout_seconds=None, init_timeout_seconds=None, num_workers=None, max_executor_processes_per_worker=None))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn refresh_column_async<'py>(
|
||||
self_: PyRef<'py, Self>,
|
||||
column: String,
|
||||
r#where: Option<String>,
|
||||
batch_size: Option<i64>,
|
||||
max_concurrency: Option<Bound<'py, PyAny>>,
|
||||
publication_interval_rows: Option<i64>,
|
||||
resource_budget: Option<Bound<'py, PyDict>>,
|
||||
gpu_class: Option<String>,
|
||||
@@ -2351,6 +2382,7 @@ impl Table {
|
||||
let options = refresh_column_options(RefreshColumnArgs {
|
||||
where_clause: r#where,
|
||||
batch_size,
|
||||
max_concurrency: positive_u32_integer(max_concurrency.as_ref(), "max_concurrency")?,
|
||||
publication_interval_rows,
|
||||
resource_budget: parse_resource_budget(resource_budget.as_ref())?,
|
||||
gpu_class,
|
||||
|
||||
Reference in New Issue
Block a user