【发布时间】:2021-03-11 09:55:00
【问题描述】:
我正在为 Google Cloud Dataflow 开发一个 ETL 管道,其中我有几个分支 ParDo 转换,每个转换都需要一个本地音频文件。然后将分支结果合并并导出为文本。
这最初是在单台机器上运行的 Python 脚本,我正在尝试使用 GC 数据流来适应 VM 工作者并行化。
提取过程从单个 GCS 存储桶位置下载文件,然后在转换完成后将其删除,以控制存储。这是由于需要对文件进行本地访问的预处理模块。这可以通过自己重写一些预处理库来重新设计以处理字节流而不是文件 - 但是,这方面的一些尝试并不顺利,我想首先探索如何处理并行化的本地Apache Beam / GC Dataflow 中的文件操作,以便更好地理解框架。
在这个粗略的实现中,每个分支都会下载和删除文件,并进行大量双重处理。在我的实现中,我有 8 个分支,因此每个文件都被下载和删除 8 次。是否可以将 GCS 存储桶安装在每个工作人员身上,而不是从远程下载文件?
或者是否有另一种方法可以确保将正确的文件引用传递给工作人员,以便:
- 单个
DownloadFilesDoFn()可以批量下载 - 然后将
PCollection中的本地文件引用扇出到所有分支 - 然后最后一个
CleanUpFilesDoFn()可以删除它们 - 如何并行化本地文件引用?
如果无法避免本地文件操作,Apache Beam / GC Dataflow 的最佳分支ParDo 策略是什么?
为简单起见,我现有实现的一些示例代码带有两个分支。
# singleton decorator
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class Predict():
def __init__(self, model):
'''
Process audio, reads in filename
Returns Prediction
'''
self.model = model
def process(self, filename):
#simplified pseudocode
audio = preprocess.load(filename=filename)
prediction = inference(self.model, audio)
return prediction
class PredictDoFn(beam.DoFn):
def __init__(self, model):
self.localfile, self.model = "", model
def process(self, element):
# Construct Predict() object singleton per worker
predict = Predict(self.model)
subprocess.run(['gsutil','cp',element['GCSPath'],'./'], cwd=cwd, shell=False)
self.localfile = cwd + "/" + element['GCSPath'].split('/')[-1]
res = predict.process(self.localfile)
return [{
'Index': element['Index'],
'Title': element['Title'],
'File' : element['GCSPath'],
self.model + 'Prediction': res
}]
def finish_bundle(self):
subprocess.run(['rm',self.localfile], cwd=cwd, shell=False)
# DoFn to split csv into elements (GSC bucket could be read as a PCollection instead maybe)
class Split(beam.DoFn):
def process(self, element):
Index,Title,GCSPath = element.split(",")
GCSPath = 'gs://mybucket/'+ GCSPath
return [{
'Index': int(Index),
'Title': Title,
'GCSPath': GCSPath
}]
管道的简化版本:
with beam.Pipeline(argv=pipeline_args) as p:
files =
(
p | 'Read From CSV' >> beam.io.ReadFromText(known_args.input)
| 'Parse CSV into Dict' >> beam.ParDo(Split())
)
# prediction 1 branch
preds1 =
(
files | 'Prediction 1' >> beam.ParDo(PredictDoFn(model1))
)
# prediction 2 branch
preds2 =
(
files | 'Prediction 2' >> beam.ParDo(PredictDoFn(model2))
)
# join branches
joined = { preds1, preds2 }
# output to file
output =
(
joined | 'WriteToText' >> beam.io.Write(beam.io.textio.WriteToText(known_args.output))
)
【问题讨论】:
-
这对我来说看起来很专业。我认为这可能已经是最佳解决方案。多年来,我一直在使用类似但不太干净的东西,GCS 访问步骤并不是一个重大的延迟或问题的根源。
-
当我从 Dataflow 访问 GCS 中的文件时,我使用
google.cloud.storage.Client(...).download_blob_to_file(),我发现它比subprocess调用gsutil干净得多 -
感谢 Steven - 我将使用它而不是
gsutil- 我能问一下管道分支时如何处理文件操作吗?实际上,我在管道中有 7 个以上的分支,用于处理数 TB 的数据。目前每个分支都在下载和删除同一个文件
标签: python google-cloud-platform google-cloud-dataflow apache-beam