UDF
使用 MaxCompute UDF 过程中常见报错问题
1. 查看 UDF 函数信息
假设UDF 函数名称为 example_function,在odpscmd 客户端执行 desc function example_funct ion;,或在 datastudio 中新建 SQL 节点后输入 desc function example_function;执行。可以得到输出示例如下:
Name |
example_function |
Owner |
xxxx |
Created Time |
2021-03-13 22:52:28 |
Class |
pyudf_test.SampleUDF |
Resources |
pyudf_test.py, numpy.zip, table_resource_example |
输出各项的含义:
- Created Time:UDF 函数在MaxCompute 中注册的时间
- Class:声明 UDF 的“Python脚本名.类名”,区分大小写
- Resources:UDF 函数依赖的资源列表,依赖的三方包和表资源也需要在资源列表里(例如上面的 numpy.zip 和table_resource_example)
需要注意的是:类名(Class)里的 Python 脚本名为 底层唯一标识的资源名 。MaxCompute 的资源名大小写不敏感,如果第一次上传资源时资源名为 pyudf_test.py,后面在 datastudio 重命名资源、或者用 odpscmd 覆盖资源时将资源名重命名为 PYUDF_TEST.py,此时 底层唯一标识的资源名 仍然为 pyudf_test.py,因此注册 UDF 函数时,类名仍然需要填 pyudf_test.SampleUDF。od pscmd 可以通过 list resource; 命令查看所有资源 底层唯一标识的资源名。
2. 查看资源信息
假设资源名称为 pyudf_test.py,在 odpscmd 客户端执行 desc resource pyudf_test.py;,或在 datastudio 中新建 SQL 节点后输入 desc resource pyudf_test.py;执行。可以得到输出示例如下:
Name |
pyudf_test.py |
Owner |
xxx |
Type |
PY |
Comment |
xxx |
CreatedTime |
2021-03-13 22:52:28 |
LastModifiedTime |
2021-03-13 22:52:28 |
LastUpdator Size |
155 |
Md5sum |
62f3e01697db19c6e318dde67e6e9626 |
输出各项的含义:
- Type:资源类型
- CreatedTime:资源创建时间
- LastModifiedTime:资源最后更新时间 l Md5sum:资源文件的 md5 值
需要注意的是:因为 MacCompute 的资源名大小写不敏感,resource_A 和 resource_a 是同一资源,上面 “Name” 项的输出和 desc resource <resource_name>; 里输入的<resource_name>
大小写一致。
3. 性能问题
1 kInstanceMonitorTimeout, usually caused by bad udf performance
报错原因:默认情况下 UDF 处理 1024 条输入数据的总时长超过 1800 秒报错。
排查方法:
- 在 UDF 中加一些日志,检查 UDF 是否有死循环,处理特定数值的耗时是否有异常,UDF 里打印日志的方法参考后文中的打印日志部分。
- 在 UDF 数据处理逻辑的头尾打印日志,预估 UDF 的执行时间。
解决方法:
- 修复 UDF 中存在的死循环等问题。
- 如果 UDF 预计的运行时间本身就很长,可以通过调整以下两个参数避免超时报错,设置这两个参数后,UDF 处理 x 条输入数据,超过 y 秒则超时报错。
- set odps.sql.executionengine.batch.rowcount=x;
- set odps.sql.udf.timeout=y; (y 最大值为 3600)
4. 线上 UDF 性能比单机测试性能差很多
2 function or view 'xxx' cannot be resolved
- 确认使用 UDF 函数的项目和注册 UDF 函数的项目一致,请注意分辨是生产还是开发项目。
- 进入使用 UDF 函数的项目
- 查看 UDF 函数信息
- 检查输出结果中“Class”是否正确,如果不对,需要重新注册函数。示例中 pyudf_test 是python文件 pyudf_test.py 的名字,SampleUDF 是 pyudf_test.py 中定义 UDF 函数的类名(注意区分大小写)。
- 检查输出结果中“Resources”是否正确、完整,如果不对,需要重新注册函数。UDF 里如果需要引用文件资源(get_cache_file)、表资源(get_cache_table)、压缩包资源(get_cache_archive)、第三方包,注册函数时要将这些资源填到资源列表里。
- 查看资源信息,检查输出结果中资源的类型“Type”和更新时间“LastModifiedTime”是否正确:
- get_cache_file 使用的文件资源类型为 FILE
- get_cache_table 使用的表资源类型是 TABLE
- get_cache_archive 或三方包的资源类型是 ARCHIVE
确认是否已将 datastudio 中的资源正确同步到 MaxCompute,如果 3.b 输出的 LastModifiedTime 不是最新的,可能是资源从datastudio 同步到 MaxCompute 有延迟,资源还没更新。
- 确认使用的是 Python2 还是 Python3 UDF。
- 如果没有项目级别打开 Python3 UDF,作业默认会使用 Python2 UDF,此时 Python 文件中如果有非 ASCII 编码字符,会报错:SyntaxError: Non-ASCII charactor '\xe8' in file xxx. online yyy.
- 如果要使用 Python3 UDF,需要在 SQL 语句前加上 set odps.sql.python.version=cp37; 文档参考 Python3UDF。
3 IOError: Download resource: xxx.zip failed, Perhaps you forgot to add it to using list when create funciton odps.distcache.DistributedCacheError: Invalid relative path, file not exists uxi job failed, caused by: Download resource failed:
xxx.zip 场景:Python UDF 里使用 get_cache_archive('xxx.zip')时报错。
排查步骤:
- 查看 UDF 函数信息(desc function your_udf_function;)确认输出的“Resources”里是否有 xxx.zip。
- 查看资源信息(desc re 链接 source xxx.zip;)确认输出的“Type”是否是 ARCHIVE。
- 确认资源名字和实际文件格式一致。如果资源名字为 xxx.zip,但实际上传的文件是 xxx.tar.gz,此时会按 zip 格式解压,会解压失败报错。
- 解决方法:上传 xxx.zip 为 ARCHIVE 类型资源,注册函数时资源列表里要加上 xxx.zip。
- get_cache_archive 使用方法参考。
4 odps.distcache.DistributedCacheError: Table resource "xxx_table_name" not found. You should declare to use it when creating your function. 场景:Python UDF 里使用 get_cache_table(xxx_table_name)时报错。
排查步骤:
- 查看 UDF 函数信息(desc function your_udf_function;)确认输出的“Resources”里是否有 xxx_table_name
- 查看资源信息(desc resource xxx_table_name;)确认输出的“Type”是否是 TABLE
解决方法:上传xxx_table_name 为 TABLE 类型资源,注册函数时资源列表里要加上 xxx_table_name get_cache_table 使用方法参考。
5 ImportError: No module named 'xxx'
找不到三方包时:
- 查看资源信息(desc resource resource-of-xxx.zip;) 检查三方包是否已上传为 MaxCompute ARCHIVE 类型资源,检查资源信息里的 Type、LastModifiedTime、Md5sum 是否符合预期。
- 查看 UDF 函数信息,检查注册函数时类名填写是否正确、三方包资源是否已填到资源列表里。
- 检查 UDF 代码里是否将三方包的路径加到 system path 中。
- 假设模块名为 xxx 时,需要确定文件夹 xxx 或文件 xxx.py 所在路径。
在 UDF 里把这个路径加到 system path 中。
- 确定文件所在路径的方法:
如果将文件夹resource_dir 直接压缩为 resource-of-xxx.zip。那么 UDF 运行时文件夹
resource_dir 所在路径为work/resource-of-xxx.zip/resource_dir/ 如果选择文件夹resource_dir 内所有文件压缩为 resource-of-xxx.zip(即不包含 resource_ dir)。那么 UDF 运行时 resource_dir 里的某个文件比如 xxx.py 所在路径为 work/resour
ce-of-xxx.zip/resource-of-xxx.zip 在本地解压后,假设 xxx 模块所在文件的相对路径为 path1/path2/xxx.p y、或 xxx 模块文件夹的相对路径为 path1/path2/xxx。则需要在 UDF 代码添加如:sys.path. insert(0, 'work/resource-of-xxx.zip/path1/path2')。(ARCHIVE 资源默认放在 UDF 执行路径的相对路径 ./work/中)
- 三方包如果是 whl 包,请检查 whl 包的后缀是否正确,python2 要求名字包含“cp27-cp27m-manylinux1_x86_64”,python3要求名字包含“cp37-cp37m-manylinux1_x86_64”。whl 文件直接改后缀为 zip,不要对 whl 文件再次打包生成 zip 文件。
- 三方包如果不是 whl 包,但是是纯 python 包,请参考第 3 步检查压缩包的目录结构,需要将 x xx 文件夹或 xxx.py 的上层目录路径加到 sys.path 中。
- 三方包如果不是 whl 包,也不是纯 python 包,检查里面是否有 setup.py,如果有 setup.py,需要在兼容环境编译后再重新上传使用,编译方法参考。
- 检查 UDF 文件名和要引用的三方包模块名是否冲突。如果 UDF 文件是 xxx.py,import xxx 时默认会导入 xxx.py 而不是三方包里的模块,此时需要修改 UDF 文件名。
- 检查报错的 import 语句是否写在 UDF 所在类的外部。如果是,需要移到 UDF 所在类内部 import。因为语法检查阶段不会下载三方包,即使尝试将三方包路径加到了 sys path 中,编译检查时也会报错 ImportError。
6 Python3标准库 ImportError Python3.7标准库列表。
7 ImportError: No module named enum ImportError: No module named request
如果没有项目级别打开Python3 UDF,作业默认会使用 Python2 UDF,不包含 Python3 的一些标准库如 enum、urllib.request 等。
如果要使用 Python3 UDF,需要在 SQL 语句前加上 setodps.sql.python.version=cp37;,文档参考 Python3UDF。
8 SyntaxError: Non-ASCII charactor '\xe8' in file xxx. on line yyy
报错原因:作业使用 Python2 UDF,且 Python 文件中如果有非 ASCII 编码字符。
解决方法:
- 在 SQL 语句前加上 set odps.sql.python.version=cp37;使用 Python3 UDF,文档参考 Python3 UDF。
- 将 Python2 的默认编码方式修改为 UTF-8,即在 Python 文件开头加上如下语句:
import sys reload(sys) sys.setdefaultencoding('utf-8')
9 UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position x-y: ordinal not in range(128)
报错原因:使用 Python2 UDF 时,annotate 中返回值类型是 string,UDF 里返回了一个unicode 类型的 Python Object(假设是ret)。此时默认会返回 str(ret),对 ret 使用 ascii 编码。
解决方法:UDF 将unicode 转为 str 后再输出,例如 returnret.encode('utf-8')
10 UnicodeDecodeError: 'utf-8' codec can't decode byte xxx in position xxx: invalid continuation byte
报错原因:使用 Python3 UDF 时,annotate 中输入参数类型是 string,但是输入的 odps string 不能按 utf-8 解码为 str 类型的 Python Object。
解决方法:
- 避免向 odps 表写入非 utf-8 编码的字符串。例如:
Python2 UDF 返回的 Python Object 是按 gbk 编码的 str,可以被正常写入 odps 表,但无法被 Python3 UDF 读出,建议返回 ret.decode('gbk').encode('utf-8')
- 在 SQL 中用内置函数 is_encoding 提前过滤掉非 utf-8 编码的数据。例如:
select py_udf(input_col) from example_tablewhere is_encoding(input_col, 'utf-8', 'utf-8') = true;
- annotate 中输入参数的类型改成 binary,改写 SQL 将 string 列转为binary 列再输入到 UDF 中。此时 UDF 中输入的 Python Object 是 bytes。例如:
select py_udf(cast(input_col as binary)) from example_table;
- resolve annotation of class xxx for UDTF yyy contains invalid content
'<EOF>', please check format and type 可能原因:UDF/UDTF/UDAF 输入或输出为复杂类型时,annotate 里的函数签名不合法。
合法的函数签名请参考:Python3UDF-参数与返回值,需要特别注意array/map/struct 等复杂类型的写法。
12 Script exception - ValueError: unmarshallable object
报错原因:Python UDAF 声明 buffer 里的元素不是 marshal对象,常见于使用复杂类型 buffer 时。解决方法:
- 给 buffer 里的元素赋值时,要检查赋的值是否是 marshal 对象。
- 假设 UDAF 里要使用两个 buffer,类型分别为 list 和 dict,那么new_buffer 方法里应该return [list(), dict()],iterate/merge/terminate 方法里使用 buffer/pbuffer时,list 类型的 buffer 对应 buffer[0]/pbuffer[0]、dict 类型的 buffer 对应 buffer[1]/pbuffer[1]。
- 如果 buffer 里的元素是 list 或dict,list 里的元素、dict 的 key/value 也必须是 marshal对象。
13 TypeError: expected <class 'xxx'> but <class 'yyy'> found, value:zzz
报错原因:Python UDF 返回的 python 数据类型(yyy)与odps 期望的 python 数据类型(xxx)不一致。
解决方法:请检查UDF annotate里写的返回值类型和Python代码里实际返回的数据类型是否匹配。
14 Semantic analysis exception - expect xxx aliases but have 0
SQL 语句中调用 UDTF 函数时写法错误,例如 UDTF 函数名为 my_udtf,输入 2 列,输出 3列,正确的写法为:
select my_udtf(col0, col1) as (ret_col0, ret_col1, ret_col2) from tmp1;
15 Semantic analysis exception - evaluate function in class xxx.yyy for user defined function zz does not match annotation ***->***
报错原因:annotate里声明的输入参数个数与 Python UDF/UDTF/UDAF 中 evaluate/process/ iterate 方法的入参个数不一致。
16 Semantic analysis exception - failed to get Udf info from xxx.py
报错原因 1:加载 UDTF/UDAF 基类的写法不对,比如写成了 import odps.udf.BaseUDTF 或 import odps.udf.BaseUDAF。
解决方法 1:修改为 from odps.udf import BaseUDTF 或 from odps.udf import BaseUDTF。
报错原因 2:UDF 所在类外面有 import 三方包的语句。因为语法检查阶段不会下载三方包,即使尝试将三方包路径加到了 sys path 中,编译检查时也会报错无法 import。解决方法 2:将 import 三方包的语句改写到 UDF 类内部。
17 Semantic analysis exception - download resource xxx.py failed, detail message: ODPS-0020011:Invalid parameter - Resource conflict for name xxx.py the candicate projects are project_a and project_b
报错原因:同一个作业中有两个UDF 依赖了不同 project 下的同名资源。
解决方法:
- 排查作业依赖的所有 UDF(包括视图中依赖的 UDF),检查 UDF 所属 project 和对应资源的名称。
- 修改依赖的 UDF 或资源名称,避免依赖不同 project 下的同名资源。
18 RuntimeError: xxx has been blocked by sandbox
报错原因:用户 Python2 UDF 内的某些函数调用被沙箱阻断了。
解决方法:
- 在 SQL 语句前设置参数 set odps.isolation.session.enable=true; (区分大小写)。
- 使用 Python3 UDF,会默认设置上 set odps.isolation.session.enable=true;
19 Python UDAF buffer size overflowed: 2821486749
报错原因:Python UDAF 中的 buffer marshal 过后的大小超过2GB,用户使用 buffer 的方式有误,buffer 的大小不应该随数据量递增。
解决方法:重新设计 UDAF 的逻辑,buffer 的大小不应该随数据量递增。例如声明了一个 buffer 是list,iterate 和 merge 阶段不能一直往 buffer 里 append 数据。
20 GLIBCXX_3.4.21 not found(GLIBC、CXXABI 同理)
报错原因:报错的 so 文件依赖的 GLIBCXX 版本高于 odps 本身的 GLIBCXX 版本。解决方法:报错一般出现在用户引入的三方包内的so,用户编译该 so 使用的 gcc 版本比 ODPS 兼容的gcc 版本高,需要使用兼容的 wheel 包、或者在兼容的环境中重新编译 so。附:ODPS 支持的二进制可执行文件或 so 文件依赖的最大版本:
GLIBC <= 2.17
CXXABI <= 1.3.8
GLIBCXX <= 3.4.19
GCC <= 4.2.0
21 libprotobuf FATAL
相关报错信息在 StdErr 中出现,报错原因一般是用户三方包内依赖的 protobuf 库和 ODPS 本身依赖的 protobuf 库版本不兼容。
22 pytorch报错 process killed by signal 6
目前已知使用 1.3.1、1.4.0 版本的pytorch 会报错,使用 1.5.0 版本没有问题。
23 OpenCV报错ImportError: libGL.so.1: cannot open shared object file: No such file or directory
5. 打印日志
- Python2:
sys.stdout.write('your log') sys.stdout.flush()
- Python3:
print('your log', flush=True)
执行时日志将输出到 logview 的stdout 中:
6. 常用文档链接
- 添加资源:
- 注册函数
- DF 使用第三方包示例。
>>快来点击免费下载《阿里云MaxCompute百问百答》了解更多详情!<<