我有大约30,000个非常小的JSON文件,我试图将它们加载到Spark数据帧中(从已安装的S3存储桶中)。据报告here和here可能存在性能问题,并被描述为Hadoop Small Files Problem
。与以前的报告不同,我没有递归到目录中(因为我的所有JSON文件都在一个子文件夹中)。我的加载JSON文件的代码如下所示。
val df = spark
.read
.option("multiline", "true")
.json("/mnt/mybucket/myfolder/*.json")
.cache
到目前为止,我的工作似乎“卡住了”。我看到两个阶段。
Job 0, Stage 0
相当快,不到1分钟。 Job 1, Stage 1
然而,要花很长时间才能显示出来(失去时间的轨迹,但是在两者之间,我们正在交谈20分钟以上),并且当它确实显示在工作界面上时,似乎“卡住了” ”(15分钟后,我仍在等待任何进度报告)。有趣的是,Job 0, Stage 0
有200个任务(我看到正在使用7个执行程序),而Job 1, Stage 1
只有1个任务(似乎仅在使用1个节点/执行程序!这真是浪费!)。
有什么方法可以使看似简单的步骤更快或更高效地加载30,000个文件?
我想到的只是将这些文件“合并”为大文件;例如,将1,000个JSON文件合并为30个更大的文件(使用NDJSON)。但是,我对这种方法持怀疑态度,因为合并文件(比如使用Python)本身可能会花费很长时间(类似于此目录中的本机linux ls
命令需要很长的时间才能返回);而且,这种方法可能无法达到端到端群集计算的目的(不是很优雅)。
答案 0 :(得分:3)
将JSON文件合并为换行符分隔的,更大的文件(目标是一个或最多10个文件,而不是30个)是这里的唯一选择。
打开30K文件的Python不会比您已经做的要慢,只是不会被分发。
除此之外,multiline=true
仅在您已经有一个非常大的JSON文件并且它是一个要存储的顶级数组或对象的情况下才特别添加。在存在该选项之前,“ JSONLines”是Spark可以读取的唯一格式。
这里最一致的解决方案是修复写入所有这些文件的提取管道,以便您可以提前累积记录,然后转储更大的批次。或者只是使用Kafka而不是从S3(或任何类似的文件系统)中读取数据
答案 1 :(得分:0)
有两个HTTP请求读取,一个HEAD,一个GET;如果所有文件都保存在同一个目录中,则列表开销只是一个LIST / 5000对象,因此需要6个列表调用。 30K HEAD&GET通话费用约为$ 25。
如果您正在使用spark列出清单并从每个文件生成一条记录,以及为每个文件安排任务的开销。您可以做一个技巧,使清单本身(在.py中完成)成为输入RDD(即,每个文件一行),而map()成为该文件的读取和地图的输出代表单个文件的记录。 scala example。这解决了火花调度的开销,因为该输入清单将被拆分成更大的部分发送给工作人员,因此仅留下那些HTTP HEAD / GET调用。
要使其有效运行,请使用Hadoop 2.8+ Jars,并使用FileSystem.listFiles(Path, true)
列出路径下整个目录树的单个递归列表,因此最好使用S3 LIST API。
(完成此操作后,为什么不将代码发布到其他地方?)