Несколько заданий spark, добавляющих данные parquet к одному базовому пути с разделением

У меня есть несколько заданий, которые я хочу выполнить параллельно, что дописать данные в один и тот же путь, используя перегородки.

например

dataFrame.write().
         partitionBy("eventDate", "category")
            .mode(Append)
            .parquet("s3://bucket/save/path");

Задание 1-категория = " billing_events" Задание 2-категория = "click_events"

оба этих задания будут усекать все существующие разделы, которые существуют в ведре s3 до выполнения, а затем сохранять полученные файлы паркета в соответствующие разделы.

то есть

задание 1 - > s3://bucket/save/path / eventDate=20160101 / channel=billing_events

Задание 2 - > s3://bucket/save/path / eventDate=20160101 / channel=click_events

проблема im сталкивается с временными файлами, которые создаются во время выполнения задания spark. Он сохраняет рабочие файлы в базовый путь

s3://bucket/save/path / _temporary/...

таким образом, оба задания в конечном итоге разделяют одну и ту же папку temp и вызывают конфликт, который, как я заметил, может привести к одному заданию удалите временные файлы, и другое задание завершится неудачей с 404 из s3, говоря, что ожидаемый временный файл не существует.

кто-нибудь сталкивался с этой проблемой и придумать стратегию параллельного выполнения заданий в той же пути?

im с помощью spark 1.6.0 на данный момент

3 ответов


поэтому после долгого чтения о том, как решить эту проблему, я подумал, что я передаю некоторую мудрость здесь, чтобы обернуть вещи. В основном благодаря комментариям Тала.

Я также обнаружил, что запись непосредственно в s3: / / bucket / save / path кажется опасной, потому что, если задание убито, а очистка временной папки не происходит в конце задания, кажется, что его оставили там для следующего задания, и я заметил, что иногда файлы предыдущих убитых заданий temp попадают в s3: / / bucket / save / path и вызывает дублирование... Абсолютно ненадежно...

кроме того, операция переименования файлов папки _temporary в соответствующие файлы s3 занимает ужасное количество времени (около 1 сек на файл), поскольку S3 поддерживает только копирование/удаление, а не переименование. Кроме того, только экземпляр драйвера переименовывает эти файлы с помощью одного потока, так как 1/5 некоторых заданий с большим количеством файлов / разделов тратятся только на ожидание переименования оперативный.

Я исключил использование DirectOutputCommitter по ряду причин.

  1. при использовании в сочетании с режимом спекуляции это приводит к дублированию (https://issues.apache.org/jira/browse/SPARK-9899)
  2. задач неудачи оставят беспорядок, который было бы невозможно найти и удалить/очистить позже.
  3. Spark 2.0 полностью удалил поддержку этого и не обновил путь существует.(https://issues.apache.org/jira/browse/SPARK-10063)

единственный безопасный, эффективный и согласованный способ выполнения этих заданий-сначала сохранить их в уникальной временной папке (уникальной по applicationId или timestamp) в hdfs. И скопируйте в S3 по завершении задания.

Это позволяет выполнять параллельные задания, поскольку они будут сохранены в уникальных временных папках, не нужно использовать DirectOutputCommitter, поскольку операция переименования в HDFS быстрее, чем S3, и сохраненные данные более последовательны.


Я подозреваю, что это связано с изменениями в обнаружении разделов, которые были введены в Spark 1.6. Изменения означают, что Spark будет обрабатывать только такие пути, как .../xxx=yyy/ как разделы, если вы указали параметр "basepath" (см. Примечания к выпуску Spark здесь).

поэтому я думаю, что ваша проблема будет решена, если вы добавите опцию basepath, например:

dataFrame
  .write()
  .partitionBy("eventDate", "category")
  .option("basepath", "s3://bucket/save/path")
  .mode(Append)
  .parquet("s3://bucket/save/path");

(у меня не было возможности проверить это, но, надеюсь, это сделает трюк:))


вместо использования partitionBy фрейм данных.писать.)( partitionBy ("eventDate", " категория") .режим (добавить) .паркет ("s3: / / bucket / save / path");

в качестве альтернативы вы можете записать файлы как

в задании-1 укажите путь к файлу паркета как фрейм данных.писать.)(режим(добавить).паркет ("s3://bucket/save/path/eventDate=20160101/channel=billing_events")

& в задании-2 укажите путь к файлу паркета как фрейм данных.писать.)(режим(добавить).паркет ("s3://bucket/save/path/eventDate=20160101/channel=click_events")

  1. оба задания создадут отдельный каталог _temporary в соответствующей папке, поэтому проблема параллелизма будет решена.
  2. и обнаружение разделов также произойдет как eventDate=20160101 и для столбца канала.
  3. недостаток-даже если channel=click_events не существует в данных, все равно файл паркета для канала=click_events будет быть созданным.