Тест производительности: Apache Parquet против Apache Avro

Автор оригинальной публикации: Дон Дрейк (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 секунды быстрее, то есть различия в полученных результатах несущественны.

На диаграмме ниже показано время выполнения в секундах.

Тест 1. Запись в файл (узкий набор данных)

Следующий тест оценивает время, затрачиваемое на простой подсчет количества строк. Результат для несжатого CSV-формата показан просто для сравнения, а также чтобы мотивировать вас не использовать этот формат в Hadoop. В этом тесте Avro и Parquet показали одинаковые результаты.

Тест 2. Подсчет количества строк (узкий набор данных)

Далее я протестировал запрос 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 раза быстрее.

Тест 3. Запрос GROUP BY (узкий набор данных)

В четвертом тесте я вызвал функцию map() для DataFrame, чтобы сымитировать обработку полного набора данных. Данная операция подсчитывает количество столбцов в каждой строке.

def numCols(x: Row): Int = {

  x.length

}

val numColumns = narrow_parq.rdd.map(numCols).distinct.collect

Этот запрос не имеет практического смысла, но он инициирует обработку всего объема данных, что нам и нужно. И снова Parquet оказался почти в 2 раза быстрее Avro.

Тест 4. Обработка полного набора данных (узкий набор данных)

Последний тест позволяет сравнить расход дискового пространства. На следующей диаграмме показаны размеры файлов в байтах. В рамках этого теста Avro применял сжатие с помощью кодека Snappy, а Parquet работал с настройками по умолчанию.

Parquet-файла оказался на 25% меньше, чем Avro-файл.

Тест 5. Размер файла (узкий набор данных)

Результаты для широкого набора данных

Я выполнил те же тесты с широким набором данных. Напомню, что он содержит 103 столбца и 694 миллиона строк при размере 194 ГБ.

В первом тесте сравним время, необходимое для записи широкого набора данных в файл в формате Avro и Parquet. В данном тесте Parquet опередил Avro.

Тест 1. Запись в файл (широкий набор данных)

При подсчете количества строк Parquet существенно вырвался вперед и позволил получить результат менее чем за 3 секунды.

Тест 2. Подсчет количества строк (широкий набор данных)

Parquet также оказался лидером при обработке более сложного запроса GROUP BY.

Тест 3. Запрос GROUP BY (широкий набор данных)

При выполнении операции map() на полном объеме данных Parquet снова опередил Avro.

Тест 4. Обработка полного набора данных (широкий набор данных)

В последнем тесте, оценивающем расход дискового пространства, впечатляющие результаты показали оба формата. Parquet позволил сжать CSV-файл размером 194 ГБ до 4,7 ГБ, а Avro – до 16,9 ГБ, что соответствует высокой степени сжатия: 97,56% и 91,24% соответственно.

Тест 5. Размер файла (широкий набор данных)

Выводы

В каждом тесте Parquet был либо наравне с конкурентом, либо превосходил его. Более высокая скорость Parquet при работе с широким набором данных частично обусловлена более высокой степенью сжатия: Spark пришлось читать в 3,5 раза меньше данных в формате Parquet, чем в формате Avro.

При выборе формата файлов для Hadoop играют роль многие факторы: интеграция со сторонними приложениями, требования к схеме, поддержка определенных типов данных и производительность. Если же производительность имеет для нас первостепенное значение, тогда согласно представленным выше результатам, мы должны остановить свой выбор на Parquet.

комментариев 5

  1. Anna:

    Отличная статья, спасибо! А почему не показываются графики со сравнением форматов Avro и Parquet, хотя в тексте идет ссылка на визуализацию проведенных экспериментов?

    Ответить

Добавить комментарий

Ваш адрес email не будет опубликован.

закрыть

Поделиться

Отправить на почту
закрыть

Вход

закрыть

Регистрация

+ =