Автор оригинальной публикации: Дон Дрейк (Don Drake)
Перевод Станислава Петренко
В течение последних нескольких месяцев среди моих коллег было множество дискуссий о достоинствах и недостатках различных форматов файлов, используемых в Apache Hadoop, таких как CSV, JSON, Apache Avro и Apache Parquet. Текстовые представления (CSV и JSON) большинство специалистов отвергают по умолчанию, поэтому главными конкурентами остаются Avro и Parquet.
Чем дольше продолжались эти дебаты, тем отчетливее я понимал, что многие личные мнения по данному вопросу основаны на слухах. Например, некоторые считают, что один формат более эффективен при обработке полного набора данных, тогда как другой – при работе с подмножеством столбцов.
Как любой хороший инженер, я решил выяснить истину с помощью сравнительного тестирования производительности двух данных форматов. Далее я расскажу вам, какие были получены результаты.
Наборы данных для тестов
При тестировании очень важно использовать реальные данные. В этом случае результаты дадут нам хорошее представление о том, чего можно ожидать в реальных производственных условиях. Кроме того, важно имитировать реальные запросы. Другими словами, для хорошего сравнения недостаточно простого подсчета строк.
Из недавней практики я подобрал два набора данных, отлично подходящих для наших экспериментов. Первый из них, который мы будем называть «узким», содержит 3 столбца и 82,8 миллиона строк, что соответствует CSV-файлу размером 3,9 ГБ. Второй набор данных, назовем его «широким», содержит 103 столбца и 694 миллиона строк, что соответствует CSV-файлу размером 194 ГБ. Такой подход позволит нам узнать, как ведут себя Parquet и Avro при работе с большим и малым набором данных.
Методика тестирования
В качестве рабочей лошадки я выбрал Apache Spark 1.6. Spark в своей базовой поставке поддерживает Parquet, а для Avro и CSV существуют хорошие плагины. Для тестирования использовался кластер на основе CDH 5.5.x, состоящий из более чем 100 узлов.
Я оценил производительность каждого формата при выполнении различных операций, таких как запись набора данных в файл, обработка простого запроса, обработка сложного запроса, обработка полного набора данных. Кроме того, я сравнил расход дискового пространства при хранении одного и того же набора данных в разных форматах.
Я выполнил все тесты в оболочке spark-shell с одинаковой конфигурацией как для узкого, так и для широкого набора данных. Единственное отличие заключалось в том, что для узкого набора выделялось 50 исполнителей (executor), а для широкого – 500.
Режим :paste в spark-shell послужил просто спасителем, в том смысле, что я мог вставлять код на Scala прямо в REPL, не беспокоясь о том, что многострочные команды могут быть неправильно восприняты интерпретатором.
#!/bin/bash -x # Drake export HADOOP_CONF_DIR=/etc/hive/conf export SPARK_HOME=/home/drake/coolstuff/spark/spark-1.6.0-bin-hadoop2.6 export PATH=$SPARK_HOME/bin:$PATH # use Java8 export JAVA_HOME=/usr/java/latest export PATH=$JAVA_HOME/bin:$PATH # NARROW NUM_EXECUTORS=50 # WIDE NUM_EXECUTORS=500 spark-shell —master yarn-client \ —conf spark.eventLog.enabled=true \ —conf spark.eventLog.dir=hdfs://nameservice1/user/spark/applicationHistory \ —conf spark.yarn.historyServer.address=http://yarnhistserver.allstate.com:18088 \ —packages com.databricks:spark-csv_2.10:1.3.0,com.databricks:spark-avro_2.10:2.0.1 \ —driver-memory 4G \ —executor-memory 2G \ —num-executors $NUM_EXECUTORS \
Я записывал время выполнения каждого запроса со вкладки Job веб-интерфейса Spark. Каждый тест повторялся три раза, а затем я находил среднее значение. Во время выполнения тестов на узком наборе данных, кластер был относительно нагружен другими задачами, в то время как при работе с широким набором кластер бездействовал. Такие условия были выбраны не специально, просто так складывалась ситуация в те моменты, когда у меня было время для проведения тестов.
Предобработка данных
Загружая узкий CSV-файл, я не создавал схему, но преобразовал один столбец из типа String в тип Timestamp. Я не включил время, затраченное на это преобразование, в итоговый результат, поскольку эта операция не имеет отношения к сравнению форматов файлов.
При загрузке широкого CSV-файла, я создал схему, но затраченное на эту операцию время по той же причине не было учтено.
Ждем поддержки типов данных Date и Timestamp в Avro!
Я очень удивился, когда в процессе подготовки запросов выяснилось, что невозможно сохранить Avro-файл с типом данных Timestamp. И действительно, Avro v1.7.x не поддерживает типы данных Date и Timestamp.
Результаты для узкого набора данных
Первый тест позволяет сравнить время, необходимое для записи узкого набора данных в файл в формате Avro и Parquet (данные уже загружены в DataFrame). В среднем Avro-файл был создан всего на 2 секунды быстрее, то есть различия в полученных результатах несущественны.
На диаграмме ниже показано время выполнения в секундах.
Следующий тест оценивает время, затрачиваемое на простой подсчет количества строк. Результат для несжатого CSV-формата показан просто для сравнения, а также чтобы мотивировать вас не использовать этот формат в Hadoop. В этом тесте Avro и Parquet показали одинаковые результаты.
Далее я протестировал запрос GROUP BY, являющийся более сложным запросом к подмножеству столбцов. В данном запросе я хотел просуммировать значения столбца replacement_cost по дням. Поскольку Avro не поддерживает типы данных Date и Timestamp, мне пришлось немного подкорректировать запрос.
Запрос для Parquet:
val sums = sqlContext.sql("""select to_date(precise_ts) as day, sum(replacement_cost) from narrow_parq group by to_date(precise_ts) """)
Запрос для Avro:
val a_sums = sqlContext.sql("""select to_date(from_unixtime(precise_ts/1000)) as day, sum(replacement_cost) from narrow_avro group by to_date(from_unixtime(precise_ts/1000)) """)
В данном тесте Parquet оказался в 2,6 раза быстрее.
В четвертом тесте я вызвал функцию map() для DataFrame, чтобы сымитировать обработку полного набора данных. Данная операция подсчитывает количество столбцов в каждой строке.
def numCols(x: Row): Int = { x.length } val numColumns = narrow_parq.rdd.map(numCols).distinct.collect
Этот запрос не имеет практического смысла, но он инициирует обработку всего объема данных, что нам и нужно. И снова Parquet оказался почти в 2 раза быстрее Avro.
Последний тест позволяет сравнить расход дискового пространства. На следующей диаграмме показаны размеры файлов в байтах. В рамках этого теста Avro применял сжатие с помощью кодека Snappy, а Parquet работал с настройками по умолчанию.
Parquet-файла оказался на 25% меньше, чем Avro-файл.
Результаты для широкого набора данных
Я выполнил те же тесты с широким набором данных. Напомню, что он содержит 103 столбца и 694 миллиона строк при размере 194 ГБ.
В первом тесте сравним время, необходимое для записи широкого набора данных в файл в формате Avro и Parquet. В данном тесте Parquet опередил Avro.
При подсчете количества строк Parquet существенно вырвался вперед и позволил получить результат менее чем за 3 секунды.
Parquet также оказался лидером при обработке более сложного запроса GROUP BY.
При выполнении операции map() на полном объеме данных Parquet снова опередил Avro.
В последнем тесте, оценивающем расход дискового пространства, впечатляющие результаты показали оба формата. Parquet позволил сжать CSV-файл размером 194 ГБ до 4,7 ГБ, а Avro – до 16,9 ГБ, что соответствует высокой степени сжатия: 97,56% и 91,24% соответственно.
Выводы
В каждом тесте Parquet был либо наравне с конкурентом, либо превосходил его. Более высокая скорость Parquet при работе с широким набором данных частично обусловлена более высокой степенью сжатия: Spark пришлось читать в 3,5 раза меньше данных в формате Parquet, чем в формате Avro.
При выборе формата файлов для Hadoop играют роль многие факторы: интеграция со сторонними приложениями, требования к схеме, поддержка определенных типов данных и производительность. Если же производительность имеет для нас первостепенное значение, тогда согласно представленным выше результатам, мы должны остановить свой выбор на Parquet.
Отличная статья, спасибо! А почему не показываются графики со сравнением форматов Avro и Parquet, хотя в тексте идет ссылка на визуализацию проведенных экспериментов?