如何适当整理由Spark Streaming生成的分区实木复合地板文件

时间:2019-04-11 17:01:54

标签: apache-spark spark-streaming spark-structured-streaming

我的Spark结构化流作业连续生成实木复合地板文件,我想在过期后将其删除(比方说30天后)。

我将我的实木复合地板数据存储为RFC3339 / ISO8601中以事件日期为分区密钥的分区数据,以便在基于cron作业的HDFS级别上相当轻松地进行内务处理(删除所有使用partitionkey

但是,自从我介绍了Spark Streaming之后,Spark将元数据写入要写入数据本身旁边的名为_spark_metadata的文件夹中。如果我现在仅删除过期的HDFS文件并在整个数据集上运行spark batch-job,由于找不到文件,该作业将失败。批处理作业将读取元数据,并期望已删除的文件存在。

对此的简单解决方案是仅禁用_spark_metadata目录的创建,如此处所述:disabling _spark_metadata in Structured streaming in spark 2.3.0。但是由于我不想在定期进行批处理分析时失去读取数据的性能,因此我想知道是否有更好的解决方案。

我认为,然后我可以只使用spark进行删除,这样它就可以删除实木复合地板hdfs文件,并且更新元数据。但是,只需执行

session.sql(String.format("DELETE FROM parquet.`%s` WHERE partitionKey < " + oldestAllowedPartitionAge, path.toString()));

不起作用。 DELETE可惜是Spark中不受支持的操作...

有什么解决方法可以删除旧数据,但_spark_metadata文件夹仍在工作吗?

3 个答案:

答案 0 :(得分:2)

据我了解,_spark_metadata的主要目的是确保容错并避免列出所有要处理的文件:

  

为了在维护的同时正确处理部分故障   语义一次,每批文件被写到   唯一目录,然后原子地附加到元数据日志。什么时候   基于DataSource的实木复合地板被初始化以进行读取,我们首先   检查此日志目录并在以下情况下使用它而不是文件列表   礼物。

https://github.com/apache/spark/commit/6bc4be64f86afcb38e4444c80c9400b7b6b745de

您引用的链接(disabling _spark_metadata in Structured streaming in spark 2.3.0)解释说,问题出自检查点状态不一致-检查点生成了元数据,但后来用户手动将其删除,并且当他重新启动查询时,由于检查点期望包含元数据而失败文件。

要查看缺少元数据是否会使您的批处理失败,请查看org.apache.spark.sql.execution.datasources.DataSource#resolveRelation方法,在其中您可以找到两种情况下的模式匹配:

  // We are reading from the results of a streaming query. Load files from the metadata log
  // instead of listing them using HDFS APIs.
  case (format: FileFormat, _)
      if FileStreamSink.hasMetadata(
        caseInsensitiveOptions.get("path").toSeq ++ paths,
        sparkSession.sessionState.newHadoopConf()) =>
  case (format: FileFormat, _) =>
    val globbedPaths =
      checkAndGlobPathIfNecessary(checkEmptyGlobPath = true, checkFilesExist = checkFilesExist)

hasMetadata方法如下:

  def hasMetadata(path: Seq[String], hadoopConf: Configuration): Boolean = {
    path match {
      case Seq(singlePath) =>
        try {
          val hdfsPath = new Path(singlePath)
          val fs = hdfsPath.getFileSystem(hadoopConf)
          if (fs.isDirectory(hdfsPath)) {
            fs.exists(new Path(hdfsPath, metadataDir))
          } else {
            false
          }
        } catch {
          case NonFatal(e) =>
            logWarning(s"Error while looking for metadata directory.")
            false
        }
      case _ => false
    }
  }

如您所见,没有失败的风险(至少通过阅读代码!)。如果有的话,请提供更多背景信息,因为问题可能出在其他地方。

关于性能方面的问题,此_spark_metadata仅包含文件列表,因此,Spark当然首先需要从您的输入目录中列出文件。但是根据我的经验,这不是最昂贵的操作。例如,在AWS S3上列出包含1297个文件的目录大约需要9秒钟。之后,由您决定是要进行简单的清洁过程还是要稍微慢一些的批处理过程。如果您有更多类似的文件,也许您还应该将它们分组为更大的文件,例如256 MB或更多?

不过,如果您想保留_spark_metadata,也许可以通过清洁应用程序删除文件。但是这将具有挑战性,因为您将有2个应用程序(流和清理)在同一个数据上运行。

您可以在此处找到有关_spark_metadata的更多信息:How to change the location of _spark_metadata directory?

答案 1 :(得分:0)

这实际上是结构化流(SPARK-24295)中的已知问题之一,尽管它仅在海量输入文件中发生,并且最终用户正在采取自己的解决方法。例如,停止查询->删除旧的输入文件->手动处理元数据以清除它们->重新启动查询。

鉴于手动操作元数据并非微不足道且不理想(假设它应停止流查询,并迫使最终用户了解元数据的格式),因此建议使用SPARK-27188作为替代方案-应用保留并清除过时的数据从元数据输入文件。

答案 2 :(得分:0)

据我所知,有三种方法可以解决此问题:

1)使用spark.load(filePathsUsingGlobRegex)仅加载需要读取的文件,这样spark不需要加载所有文件,因此不需要spark_metadata。

优点:您仍然可以获得spark_metadata的好处(读取速度更快,仍然可以确保一次语义准确)

缺点:您必须自己构建文件的路径,如果您将数据存储在各种分区策略中,则可能会更麻烦。

2)不要在输出目录disabling _spark_metadata in Structured streaming in spark 2.3.0

中创建spark_metadata

优点:清理很简单

缺点:您失去了spark_metadata的好处。

3)了解并更新spark_metadata文件,在升级时删除较旧的文件。

优点:您同时拥有保留工作和spark_metadata的好处。

缺点:您必须手动更改_spark_metadata,这可能很难维护。鉴于这是内部的火花,并且可以改变。