Apache Beam Python SDK用ValueProvider读BigQuery报无projectId错误
使用Apache Beam Python 2.29.0 SDK开发流水线,按照官方文档关于table参数支持ValueProvider的说明尝试动态读取BigQuery数据,核心实现逻辑包括:
- 流水线入口将BigQuery源表名定义为ValueProvider类型参数
- 通过PipelineOptions接口使用
add_value_provider_argument注册--bq_source_table命令行动态参数 - 直接将该动态参数传入
ReadFromBigQuery算子完成读取绑定
流水线提交运行后抛出属性不存在的错误:
AttributeError: 'StaticValueProvider' object has no attribute 'projectId' [while running 'Read from BigQuery/Read/SDFBoundedSourceReader/ParDo(SDFBoundedSourceDoFn)/SplitAndSizeRestriction']
补充验证发现:将同一个ValueProvider传入query参数时可以正常运行,且不希望通过Flex Templates方案解决该问题。
该报错属于Apache Beam 2.29.0版本的官方实现缺陷:
ReadFromBigQuery的table参数在实现动态参数支持时,SDF(有界源分裂)模块的前置逻辑漏写了对ValueProvider类型的判断,直接把传入的ValueProvider对象传入了BigQuery表引用解析流程- 表引用解析逻辑需要从解析完成的字符串表名中拆分提取
projectId、datasetId、tableId字段,因此会错误地尝试从ValueProvider对象本身读取projectId属性,最终抛出上述属性不存在的错误 - 之所以
query参数传入ValueProvider可以正常工作,是因为该参数的全链路实现逻辑都做了类型判断,每次取值前都会主动调用ValueProvider的get()方法解析实际运行时的值,不存在提前绑定对象属性的问题
方案1:复用已验证正常的query参数支持逻辑
将直接读表的逻辑替换为全表扫描的查询逻辑,通过NestedValueProvider把动态表名拼接为标准SQL查询语句,利用已经稳定运行的query参数ValueProvider支持路径完成读取,示例代码:
# 基于动态传入的表名ValueProvider,嵌套生成查询语句的ValueProvider full_table_query = ValueProvider.NestedValueProvider( bq_source_table, lambda table_str: f"SELECT * FROM `{table_str}`" ) ( pipeline # 传入嵌套生成的query ValueProvider,开启标准SQL支持 | "Read from BigQuery" >> ReadFromBigQuery( query=full_table_query, use_standard_sql=True ) )
该方案无需调整依赖版本,兼容常规DirectRunner、DataflowRunner提交模式,不需要打包Flex模板。
方案2:升级Apache Beam Python SDK至缺陷修复版本
该ValueProvider适配缺陷在Beam 2.30.0及后续版本中已经被修复:修复后的SDF分裂逻辑会在处理table参数时主动判断传入值的类型,如果识别为ValueProvider类型,会先调用get()方法解析出实际的表名字符串,再转换为对应的BigQuery表引用对象,从根源上避免属性读取错误。升级依赖后即可直接按照最初的代码逻辑传值,不需要调整读取逻辑。
方案3:动态绑定读取参数绕过前置属性访问
如果必须停留在2.29.0版本且不想使用查询模式,可以在流水线运行阶段(ValueProvider可以获取实际值的阶段)再完成BigQuery读取的初始化,跳过ReadFromBigQuery在DAG构建阶段对table参数的提前属性访问,该方案需要自定义轻量的DoFn包装逻辑,将读取触发时机从DAG构建阶段后移到运行时执行阶段。
注意:以上所有方案均不要求将流水线打包为Flex Templates,常规的模板提交、直连Runner提交模式均可兼容运行。
内容的提问来源于stack exchange,提问作者LaurensVijnck

