Skip to main content

risingwave_expr/expr/
expr_udf.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::{Arc, LazyLock};
17
18use anyhow::Context;
19use await_tree::InstrumentAwait;
20use prometheus::{Registry, exponential_buckets};
21use risingwave_common::array::arrow::arrow_schema_udf::{Fields, Schema, SchemaRef};
22use risingwave_common::array::arrow::{UdfArrowConvert, UdfFromArrow, UdfToArrow};
23use risingwave_common::array::{Array, ArrayRef, DataChunk};
24use risingwave_common::metrics::*;
25use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY;
26use risingwave_common::row::OwnedRow;
27use risingwave_common::types::{DataType, Datum};
28use risingwave_expr::expr_context::FRAGMENT_ID;
29use risingwave_pb::expr::ExprNode;
30
31use super::{AsyncExpressionBoxExt, BoxedExpression, BuildBoxed};
32use crate::expr::{AsyncExpression, ExpressionInfo};
33use crate::sig::{BuildOptions, UdfImpl, UdfKind};
34use crate::{ExprError, Result, bail};
35
36#[derive(Debug)]
37pub struct UserDefinedFunction {
38    children: Vec<BoxedExpression>,
39    arg_types: Vec<DataType>,
40    return_type: DataType,
41    arg_schema: SchemaRef,
42    runtime: Box<dyn UdfImpl>,
43    arrow_convert: UdfArrowConvert,
44    span: await_tree::Span,
45    metrics: Metrics,
46}
47
48impl ExpressionInfo for UserDefinedFunction {
49    fn return_type(&self) -> DataType {
50        self.return_type.clone()
51    }
52}
53
54impl AsyncExpression for UserDefinedFunction {
55    async fn eval(&self, input: &DataChunk) -> Result<ArrayRef> {
56        if input.cardinality() == 0 {
57            // early return for empty input
58            let mut builder = self.return_type.create_array_builder(input.capacity());
59            builder.append_n_null(input.capacity());
60            return Ok(builder.finish().into_ref());
61        }
62        let mut columns = Vec::with_capacity(self.children.len());
63        for child in &self.children {
64            let array = child.eval(input).await?;
65            columns.push(array);
66        }
67        let chunk = DataChunk::new(columns, input.visibility().clone());
68        self.eval_inner(&chunk).await
69    }
70
71    async fn eval_row(&self, input: &OwnedRow) -> Result<Datum> {
72        let mut columns = Vec::with_capacity(self.children.len());
73        for child in &self.children {
74            let datum = child.eval_row(input).await?;
75            columns.push(datum);
76        }
77        let arg_row = OwnedRow::new(columns);
78        let chunk = DataChunk::from_rows(std::slice::from_ref(&arg_row), &self.arg_types);
79        let output_array = self.eval_inner(&chunk).await?;
80        Ok(output_array.to_datum())
81    }
82}
83
84impl UserDefinedFunction {
85    async fn eval_inner(&self, input: &DataChunk) -> Result<ArrayRef> {
86        // this will drop invisible rows
87        let arrow_input = self
88            .arrow_convert
89            .to_record_batch(self.arg_schema.clone(), input)?;
90
91        // metrics
92        self.metrics
93            .input_chunk_rows
94            .observe(arrow_input.num_rows() as f64);
95        self.metrics
96            .input_rows
97            .inc_by(arrow_input.num_rows() as u64);
98        self.metrics
99            .input_bytes
100            .inc_by(arrow_input.get_array_memory_size() as u64);
101        let timer = self.metrics.latency.start_timer();
102
103        let arrow_output_result = self
104            .runtime
105            .call(&arrow_input)
106            .instrument_await(self.span.clone())
107            .await;
108
109        timer.stop_and_record();
110        if arrow_output_result.is_ok() {
111            &self.metrics.success_count
112        } else {
113            &self.metrics.failure_count
114        }
115        .inc();
116        // update memory usage
117        self.metrics
118            .memory_usage_bytes
119            .set(self.runtime.memory_usage() as i64);
120
121        let arrow_output = arrow_output_result?;
122
123        if arrow_output.num_rows() != input.cardinality() {
124            bail!(
125                "UDF returned {} rows, but expected {}",
126                arrow_output.num_rows(),
127                input.cardinality(),
128            );
129        }
130
131        let output = self.arrow_convert.from_record_batch(&arrow_output)?;
132        let output = output.expand_vis(input.visibility().clone());
133
134        let Some(array) = output.columns().first() else {
135            bail!("UDF returned no columns");
136        };
137        if !array.data_type().equals_datatype(&self.return_type) {
138            bail!(
139                "UDF returned {:?}, but expected {:?}",
140                array.data_type(),
141                self.return_type,
142            );
143        }
144
145        // handle optional error column
146        if let Some(errors) = output.columns().get(1) {
147            if errors.data_type() != DataType::Varchar {
148                bail!(
149                    "UDF returned errors column with invalid type: {:?}",
150                    errors.data_type()
151                );
152            }
153            let errors = errors
154                .as_utf8()
155                .iter()
156                .filter_map(|msg| msg.map(|s| ExprError::Custom(s.into())))
157                .collect();
158            return Err(crate::ExprError::Multiple(array.clone(), errors));
159        }
160
161        Ok(array.clone())
162    }
163}
164
165impl UserDefinedFunction {
166    fn build_inner(
167        prost: &ExprNode,
168        build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
169    ) -> Result<Self> {
170        let return_type = DataType::from(prost.get_return_type().unwrap());
171        let udf = prost.get_rex_node().unwrap().as_udf().unwrap();
172        let name = udf.get_name();
173        let arg_types = udf.arg_types.iter().map(|t| t.into()).collect::<Vec<_>>();
174
175        let language = udf.language.as_str();
176        let runtime = udf.runtime.as_deref();
177        let link = udf.link.as_deref();
178
179        let name_in_runtime = udf
180            .name_in_runtime()
181            .expect("SQL UDF won't get here, other UDFs must have `name_in_runtime`");
182
183        // lookup UDF builder
184        let build_fn = crate::sig::find_udf_impl(language, runtime, link)?.build_fn;
185        let runtime = build_fn(BuildOptions {
186            kind: UdfKind::Scalar,
187            body: udf.body.as_deref(),
188            compressed_binary: udf.compressed_binary.as_deref(),
189            link: udf.link.as_deref(),
190            name_in_runtime,
191            arg_names: &udf.arg_names,
192            arg_types: &arg_types,
193            return_type: &return_type,
194            always_retry_on_network_error: udf.always_retry_on_network_error,
195            language,
196            is_async: udf.is_async,
197            is_batched: udf.is_batched,
198        })
199        .context("failed to build UDF runtime")?;
200
201        let arrow_convert = UdfArrowConvert {
202            legacy: runtime.is_legacy(),
203        };
204
205        let arg_schema = Arc::new(Schema::new(
206            udf.arg_types
207                .iter()
208                .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t)))
209                .try_collect::<Fields>()?,
210        ));
211
212        let metrics = GLOBAL_METRICS.with_label_values(
213            link.unwrap_or(""),
214            language,
215            name,
216            // batch query does not have a fragment_id
217            &FRAGMENT_ID::try_with(ToOwned::to_owned)
218                .unwrap_or(0.into())
219                .to_string(),
220        );
221
222        let children: Vec<BoxedExpression> = udf.children.iter().map(build_child).try_collect()?;
223
224        Ok(Self {
225            children,
226            arg_types,
227            return_type,
228            arg_schema,
229            runtime,
230            arrow_convert,
231            span: await_tree::span!("udf_call({})", name),
232            metrics,
233        })
234    }
235}
236
237impl BuildBoxed for UserDefinedFunction {
238    fn build_boxed(
239        prost: &ExprNode,
240        build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
241    ) -> Result<BoxedExpression> {
242        Self::build_inner(prost, build_child).map(AsyncExpressionBoxExt::boxed)
243    }
244}
245
246/// Monitor metrics for UDF.
247#[derive(Debug, Clone)]
248struct MetricsVec {
249    /// Number of successful UDF calls.
250    success_count: LabelGuardedIntCounterVec,
251    /// Number of failed UDF calls.
252    failure_count: LabelGuardedIntCounterVec,
253    /// Total number of retried UDF calls.
254    retry_count: LabelGuardedIntCounterVec,
255    /// Input chunk rows of UDF calls.
256    input_chunk_rows: LabelGuardedHistogramVec,
257    /// The latency of UDF calls in seconds.
258    latency: LabelGuardedHistogramVec,
259    /// Total number of input rows of UDF calls.
260    input_rows: LabelGuardedIntCounterVec,
261    /// Total number of input bytes of UDF calls.
262    input_bytes: LabelGuardedIntCounterVec,
263    /// Total memory usage of UDF runtime in bytes.
264    memory_usage_bytes: LabelGuardedIntGaugeVec,
265}
266
267/// Monitor metrics for UDF.
268#[derive(Debug, Clone)]
269struct Metrics {
270    /// Number of successful UDF calls.
271    success_count: LabelGuardedIntCounter,
272    /// Number of failed UDF calls.
273    failure_count: LabelGuardedIntCounter,
274    /// Total number of retried UDF calls.
275    #[expect(dead_code)]
276    retry_count: LabelGuardedIntCounter,
277    /// Input chunk rows of UDF calls.
278    input_chunk_rows: LabelGuardedHistogram,
279    /// The latency of UDF calls in seconds.
280    latency: LabelGuardedHistogram,
281    /// Total number of input rows of UDF calls.
282    input_rows: LabelGuardedIntCounter,
283    /// Total number of input bytes of UDF calls.
284    input_bytes: LabelGuardedIntCounter,
285    /// Total memory usage of UDF runtime in bytes.
286    memory_usage_bytes: LabelGuardedIntGauge,
287}
288
289/// Global UDF metrics.
290static GLOBAL_METRICS: LazyLock<MetricsVec> =
291    LazyLock::new(|| MetricsVec::new(&GLOBAL_METRICS_REGISTRY));
292
293impl MetricsVec {
294    fn new(registry: &Registry) -> Self {
295        let labels = &["link", "language", "name", "fragment_id"];
296        let labels5 = &["link", "language", "name", "fragment_id", "instance_id"];
297        let success_count = register_guarded_int_counter_vec_with_registry!(
298            "udf_success_count",
299            "Total number of successful UDF calls",
300            labels,
301            registry
302        )
303        .unwrap();
304        let failure_count = register_guarded_int_counter_vec_with_registry!(
305            "udf_failure_count",
306            "Total number of failed UDF calls",
307            labels,
308            registry
309        )
310        .unwrap();
311        let retry_count = register_guarded_int_counter_vec_with_registry!(
312            "udf_retry_count",
313            "Total number of retried UDF calls",
314            labels,
315            registry
316        )
317        .unwrap();
318        let input_chunk_rows = register_guarded_histogram_vec_with_registry!(
319            "udf_input_chunk_rows",
320            "Input chunk rows of UDF calls",
321            labels,
322            exponential_buckets(1.0, 2.0, 10).unwrap(), // 1 to 1024
323            registry
324        )
325        .unwrap();
326        let latency = register_guarded_histogram_vec_with_registry!(
327            "udf_latency",
328            "The latency(s) of UDF calls",
329            labels,
330            exponential_buckets(0.000001, 2.0, 30).unwrap(), // 1us to 1000s
331            registry
332        )
333        .unwrap();
334        let input_rows = register_guarded_int_counter_vec_with_registry!(
335            "udf_input_rows",
336            "Total number of input rows of UDF calls",
337            labels,
338            registry
339        )
340        .unwrap();
341        let input_bytes = register_guarded_int_counter_vec_with_registry!(
342            "udf_input_bytes",
343            "Total number of input bytes of UDF calls",
344            labels,
345            registry
346        )
347        .unwrap();
348        let memory_usage_bytes = register_guarded_int_gauge_vec_with_registry!(
349            "udf_memory_usage",
350            "Total memory usage of UDF runtime in bytes",
351            labels5,
352            registry
353        )
354        .unwrap();
355
356        MetricsVec {
357            success_count,
358            failure_count,
359            retry_count,
360            input_chunk_rows,
361            latency,
362            input_rows,
363            input_bytes,
364            memory_usage_bytes,
365        }
366    }
367
368    fn with_label_values(
369        &self,
370        link: &str,
371        language: &str,
372        name: &str,
373        fragment_id: &str,
374    ) -> Metrics {
375        // generate an unique id for each instance
376        static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(0);
377        let instance_id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed).to_string();
378
379        let labels = &[link, language, name, fragment_id];
380        let labels5 = &[link, language, name, fragment_id, &instance_id];
381
382        Metrics {
383            success_count: self.success_count.with_guarded_label_values(labels),
384            failure_count: self.failure_count.with_guarded_label_values(labels),
385            retry_count: self.retry_count.with_guarded_label_values(labels),
386            input_chunk_rows: self.input_chunk_rows.with_guarded_label_values(labels),
387            latency: self.latency.with_guarded_label_values(labels),
388            input_rows: self.input_rows.with_guarded_label_values(labels),
389            input_bytes: self.input_bytes.with_guarded_label_values(labels),
390            memory_usage_bytes: self.memory_usage_bytes.with_guarded_label_values(labels5),
391        }
392    }
393}