A user-defined scalar function (UDSF) maps zero, one, or more scalar values to a single scalar value. Each input row produces exactly one output value.
This topic describes how to create, register, and use a Python UDSF in Realtime Compute for Apache Flink.
Limits
The following constraints apply when you develop Python user-defined functions (UDFs) in Realtime Compute for Apache Flink:
| Constraint | Requirement |
|---|---|
| Apache Flink version | 1.12 and later |
| Python version | Pre-installed on every workspace. VVR earlier than 8.0.11: Python 3.7.9. VVR 8.0.11 and later: Python 3.9.21. |
| JDK version | JDK 8 and JDK 11. Third-party JAR packages must be compatible with JDK 8 or JDK 11. |
| Scala version | Open-source Scala 2.11 only. Third-party JAR packages must be compatible with Scala 2.11. |
| Inline functions | Supported only in VVR 11.9-preview1 and later. |
After upgrading to VVR 8.0.11 or later, test, deploy, and run your existing PyFlink drafts again to confirm compatibility.
Development methods
You can develop a Python UDSF in either of the following ways:
-
Package your Python code, upload the package, and register it as a function on the platform.
-
Declare the code logic as an inline function in a SQL statement.
If the logic of your UDSF is simple, consider developing it as an inline function.
Create a UDSF
The following steps use Windows as the example environment. Flink provides a sample repository that includes implementations for UDSFs, user-defined aggregate functions (UDAFs), and user-defined table-valued functions (UDTFs).
-
Download and decompress python_demo-master to your local machine.
This is a third-party GitHub repository. Access may be slow or intermittent.
-
In PyCharm, choose File > Open and open the decompressed
python_demo-masterdirectory. -
Open
udfs.pyin the\python_demo-master\udxpath and define your UDSF.from pyflink.table import DataTypes from pyflink.table.udf import udf @udf(result_type=DataTypes.STRING()) def sub_string(s: str, begin: int, end: int): return s[begin:end]The
sub_stringexample extracts characters from positionbeginto positionendin the input string. -
From the
\python_demo-masterdirectory, run the following command to package theudxdirectory:zip -r python_demo.zip udxWhen
python_demo.zipappears in\python_demo-master\, the package is ready.
Register a UDSF
After creating the package, register the UDSF in the Realtime Compute for Apache Flink console. For registration steps, see Manage user-defined functions (UDFs).
Use a UDSF
After registering the UDSF, use it in a Flink SQL job.
-
Create a draft using Flink SQL. For details, see Job development overview. The following example calls
ASI_UDSF(the registered name of your UDSF) to extract characters from positions 2 to 4 of theafield in the source table:CREATE TEMPORARY TABLE ASI_UDSF_Source ( a VARCHAR, b INT, c INT ) WITH ( 'connector' = 'datagen' ); CREATE TEMPORARY TABLE ASI_UDSF_Sink ( a VARCHAR ) WITH ( 'connector' = 'blackhole' ); INSERT INTO ASI_UDSF_Sink SELECT ASI_UDSF(a, 2, 4) FROM ASI_UDSF_Source; -
In the left-side navigation pane of the development console, choose O&M > Deployments. Find the deployment, then click Start in the Actions column. After the deployment starts, characters at positions 2–4 of the
afield inASI_UDSF_Sourceare written toASI_UDSF_Sink.
Python inline scalar functions
An inline function embeds its implementation directly in a CREATE FUNCTION statement, so the function is defined and registered within the same SQL statement. The following example defines an inline function that masks email addresses. Declare the complete code logic between the $$ delimiters.
CREATE TEMPORARY FUNCTION mask_email(email STRING)
RETURNS STRING AS $$
if email is None:
return None
name, separator, domain = email.partition("@")
if not separator:
return email
return name[:1] + "***@" + domain
$$ LANGUAGE PYTHON;
-
Python is sensitive to indentation. Start the first-level code of the function body at column 0, and maintain correct relative indentation within code blocks.
-
Inline functions do not support vectorized execution and cannot be declared as non-deterministic functions.
-
Only TEMPORARY functions are supported. Function definitions cannot be persisted to the platform yet.
Asynchronous user-defined functions
For UDFs that perform I/O-intensive operations such as external database access or HTTP requests, use asynchronous user-defined functions. A single asynchronous UDF can handle multiple I/O requests concurrently, distributing wait time across requests and improving job throughput.
Limits
-
Supported only on VVR 11.7 and later. VVR PyFlink 11.7 or later is required. For details, see ververica-flink.
pip3 install "ververica-flink>=11.7" -
Only asynchronous user-defined scalar functions (UDSFs) are supported.
-
Only Python process mode is supported, that is,
python.execution-mode=process. -
Pandas asynchronous user-defined functions are not yet supported.
-
Inline functions are not yet supported.
Usage
An asynchronous user-defined function can be implemented as a Python async function or as a subclass of the asynchronous function class. Sample code is shown below.
import asyncio
from pyflink.table import DataTypes
from pyflink.table.udf import AsyncScalarFunction, udf
# Method 1: Use a Python async function
@udf(result_type=DataTypes.STRING())
async def async_api_call(product_id: str) -> str:
await asyncio.sleep(0.05)
return f"product_{product_id}"
# Method 2: Subclass the asynchronous function class
class AsyncUserLookup(AsyncScalarFunction):
def open(self, function_context):
self.cache = {}
async def eval(self, user_id: str) -> str:
if user_id in self.cache:
return self.cache[user_id]
await asyncio.sleep(0.05)
result = f"user_{user_id}"
self.cache[user_id] = result
return result
def close(self):
self.cache.clear()
async_user_lookup = udf(
AsyncUserLookup(),
input_types=[DataTypes.STRING()],
result_type=DataTypes.STRING()
)
Asynchronous user-defined functions are registered and used the same way as synchronous functions.
Configuration parameters
The following parameters control the runtime behavior of asynchronous user-defined functions.
|
Parameter |
Default value |
Description |
|
table.exec.async-scalar.max-concurrent-operations |
10 |
The maximum number of concurrent asynchronous calls per operator instance. Default value: 10. |
|
table.exec.async-scalar.timeout |
3 min |
The timeout for a single asynchronous call. |
|
table.exec.async-scalar.retry-strategy |
FIXED_DELAY |
The retry strategy after an asynchronous call fails. Supported values:
|
|
table.exec.async-scalar.retry-delay |
100 ms |
The wait time for fixed-delay retries. Note
Effective only when table.exec.async-scalar.retry-strategy is set to FIXED_DELAY. |
|
table.exec.async-scalar.max-attempts |
3 |
The maximum number of attempts before an asynchronous call is considered failed. Note
Effective only when table.exec.async-scalar.retry-strategy is set to FIXED_DELAY. |