Привет, %username%! Меня зовут Кирилл Фурзанов, я Data Scientist в Сбере, участник профессионального сообщества NTA. При формировании витрин данных и датасетов в экосистеме Hadoop одним из важных вопросов является выбор оптимального способа хранения данных в hdfs. Рассмотрим один из важных вопросов при создании витрины – выбор соответствующего формата файла для хранения.

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

Строковые форматы: csv, json, avro.

Колоночные форматы: rc, orc, parquet.

Строковые типы данных, такие как csv, json, avro сохраняют данные в строки. Данный тип позволяет достаточно быстро записывать данные в витрину, а такой формат, как avro позволяет так же динамически изменять схему данных. Однако, задачи по чтению и фильтрации данных строковых форматов требуют большего времени, чем в колоночных форматах из-за самого алгоритма чтения и фильтрации данных. Данный алгоритм можно описать в 3 этапа:

1) выбирается одна отдельная строка;

2) производится разделение этой строки на столбцы;

3) производится фильтрация данных и применение функций к соответствующим строкам.

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

CSV –  распространенный формат данных, используемый зачастую для передачи информации между различными хранилищами. Схема данных в таком формате статична и не подлежит изменению.

JSON – хранение данных на hdfs в формате json несколько отличается от привычного json-файла тем, что каждая строка представляет собой отдельный json-файл.

Примером данного формата файла может послужить следующий набор данных:

{id: 1, name: ‘Иван’, surname: ‘Иванов’, second_name: ‘Иванович’}
{id: 2, name: ‘Петр’, surname: ‘Петров’, second_name: ‘Петрович’}

Как видно из примера, основным отличием данного формата от формата CSV является его разделимость и то, что он содержит метаданные вместе с данными, что позволяет изменять исходную схему данных. Так же данный формат файлов позволяет хранить сложные структуры данных в колонках, что является еще одним отличием данного формата перед CSV форматом. К недостаткам данного формата можно отнести так же плохую сжимаемость в блоках данных и значительные затраты на хранение данного типа файлов (даже по сравнению с форматом CSV из-за необходимости дублирования метаданных для каждой строки).

Наибольшую же популярность среди строковых форматов хранения данных занимает формат avro. Данный формат представляет собой контейнер, содержащий в себе заголовок и один или несколько блоков с данными:

Заголовок состоит из:

  • ASCII слова ‘Obj1’.

  • Метаданных файла, включая определение схемы.

  • 16-байтного случайного слова (маркера синхронизации).

Блоки данных в свою очередь состоят из:

  • Числа объектов в блоке.

  • Размера серриализованых объектов в блоке.

  • Сами данные, представленные в двоичном виде с определенным типом сжатия.

  • 16-байтного случайного слова (маркера синхронизации).

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

Колоночные же форматы напротив занимают значительно большее время для записи данных, однако же они значительно быстрее решают задачи чтения и фильтрации данных, занимая при этом значительно меньшее пространство на дисках, по сравнению со строковыми форматами. Наиболее распространенными колоночными форматами являются форматы orc и parquet.

ORC (Optimized Record Columnar File) –  колоночный формат данных, разделяющий исходный набор данных на полосы, размером в 250M каждая. Колонки в таких полосках разделены друг от друга, обеспечивая избирательное чтение, что увеличивает скорость обработки такой полоски. Кроме того, данные полоски хранят в себе индексы и предикаты, что в свою очередь обеспечивает еще большую скорость чтения и фильтрации данных. Ниже приведена схема структуры ORC файла.

Parquet – это бинарный колоночно-ориентированный формат данных. Данный формат имеет сложную структуру данных, которая разделяется на 3 уровня:

  • Row-group – логическое горизонтальное разбиение данных на строки, состоящие из фрагментов каждого столбца в наборе данных.

  • Column chunk – фрагмент конкретного столбца.

  • Page – Содержит в себе наборы row-group. Данные страницы разграничиваются между собой с помощью header и footer. Header содержит волшебное число PAR1, определяющее начало страницы. Footer содержит стартовые координаты каждого из столбцов, версию формата, схему данных, длину метаданных (4 байта), и волшебное число PAR1 как флаг окончания.

Более подробно структура файла parquet представлена на следующей схеме:

Выбор определенного типа и группы форматов файлов во многом зависит от решаемой задачи.

Мой датасет состоит в основном из простейших типов данных, таких как числа, дата со временем и небольших строковых данных, в связи с чем каких-либо ограничений на выбор формата для хранения у нас нет, и основным критерием будет обеспечение наилучшего быстродействия для операций чтения и фильтрации данных, при этом обеспечивая минимальный занимаемый размер данных. В связи с этим я построил краткую матрицу, описывающую качественные характеристики вышеизложенных форматов на основе заданных критериев:

Формат

Чтение и фильтрация данных

Подходит для длительного хранения большого объема данных

Сжимаемость

csv

Медленное чтение и фильтрация

Нет

Не сжимаемый формат

json

Медленное чтение и фильтрация

Нет

Не сжимаемый формат

avro

Медленное чтение и фильтрация

Нет

Сжимаемый формат

orc

Достаточная скорость чтения и фильтрации

Да

Сжимаемый формат

(Наилучше сжатие)

parquet

Достаточная скорость чтения и фильтрации

Да

Сжимаемый формат

Формат AVRO далее не будет рассматриваться, так как не поддерживается на нашем кластере. Ниже приведена матрица сопоставления количественных характеристик данных форматов файлов на реализованном датасете, который содержит около 100 млн. строк различных простых форматов:

Формат

Занимаемый объем

Скорость чтения данных без фильтра (avg)

Скорость чтения данных с фильтром (avg)

csv

36GB

23 сек.

12 сек.

json

84GB

27 сек.

25 сек.

orc

4.9GB

4 сек.

4.4 сек.

parquet

5.8GB

6 сек.

5.9 сек.

Для большей достоверности результатов расчета были применены следующие условия:

1) Запуск задач производился с одинаковой конфигурацией для каждой из job-ов.

2) Количество запусков тестового запроса было равно 20.

3) Одинаковые запросы для каждого формата.

4) Задачи отрабатывали на свободном кластере.

5) Время чтения данных учитывалось без времени, затрачиваемом на запуск и остановку job-а.

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

Ниже приведу простой код создания таблиц с определенным форматом посредством hiveQL запроса:

CREATE TABLE tbl_name (id int, ….)
STORED AS [ORC|JSON|CSV|PARQUET|AVRO]

Или:

CREATE TABLE tbl_name 
STORED AS [ORC|JSON|CSV|PARQUET|AVRO]
As select * from another_table

И запрос на языке Spark на примере создания таблицы формата parquet:

Spark_df.write.format(‘parquet’).mode(‘overwrite’).saveAsTable(‘tablename’)

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

Комментарии (11)


  1. alexxz
    23.01.2023 15:40
    +6

    Много. Очень много вопросов по результатам тестирования.

    Количество и размер файлов на диске для разных случаев? Были ли csv и json сжаты хотя бы гзипом? И почему вы регили, что они несжимемые? Количество ресурсов на кластере? Как вы объясните, что CSV с фильтрацией оказался в два раза быстрее CSV без фильтрации? Почему время запросов с фильтром на колоночных форматах не отличается от времени запросов без фильтра?

    Вычитывание 1 столбца из 100 в колоночных форматах, вероятно, окажется весьма эффективным. А вот вычитывание 5 столбцов из 6 в колоночном формате имеет все шансы оказаться менее эффективным. Какой сетап в тестах?

    Колоночные форматы требуют больших блоков в памяти. Какое кумулятивное потребление памяти и процессора в тестах?


    1. NewTechAudit Автор
      25.01.2023 12:50

      Честно говоря, про возможность сжатия json\csv отдельно узнал уже после написания статьи в связи с чем при тестировании сжатие не использовалось.

      Про сетап, вычитывал 5 колонок из 61 с подсчетом кол-ва строк.

      Конфиг выбирал таким образом, чтобы он в среднем соответствовал используемым конфигам в работе:

      spark.executor.instances = 4

      spark.executor.core = 3

      spark.driver.inctances = 1

      spark.driver.memory = 4g

      spark.executor.memory = 8g

      overhead ~ 25% от показателя memory на driver`е и executor`е

       

      Количество файлов на данный момент не возможно получить, поскольку данные таблички уже удалены за ненадобностью

      По этой же причине не смогу вам ответить по кумулятивному потреблению памяти и процессора на тест.

      Объем на диске рассчитывался без учета фактора репликации, т.е. он соответствует фактору репликации 1, если я конечно правильно понял ваш вопрос.


  1. Yo1
    23.01.2023 20:26
    +3

    на дворе 2023 год, можно было бы что-то не из 90х потестить. на хадупе, например, сейчас delta с от датабрикса доступна с z-order индексами поверх паркета. еще есть iceberg и hudi.


  1. sshikov
    23.01.2023 21:35
    +1

    К минусам можно отнести то, что этот формат отсутствует из коробки и для возможности чтения и записи необходимо устанавливать внешний компонент Avro.

    Когда это кому-то мешало? В типичном хадуп кластере сотни, если не тысячи компонентов (в виде jar-файлов, например), какой-то один внешний компонент Авро — вообще ни разу не проблема.


    1. NewTechAudit Автор
      25.01.2023 12:24

      Вообще да, это достаточно спорный минус в общем.


      1. sshikov
        25.01.2023 19:02
        +1

        Конечно. По сравнению с тем, что методика измерения не описана вовсе — это совсем ерунда. По-моему, даже софт не был описан, который данные читал (сейчас я вижу что это спарк был). А без этого любые измерения на хадупе — как цена на бананы в африке. Ну вот реально, запустили вы в 10 или в 100 раз больше процессов спарка, и запрос выполнился быстрее в два раза. Как бы время выполнения меньше — но при этом ядра*часы, и гигабайты*часы намного хуже. Это лучше, или хуже?

        Ну или как выше вопрос задали: «вычитывание 5 столбцов из 6 в колоночном формате имеет все шансы оказаться менее эффективным» — а как по мне, надо начать с определения эффективности. Потому что паркет и сжатие — это про размен объема читаемых с диска данных на ядра процессора. Так как паркет хорошо сжимается, то объем считываемых данных (из HDFS, а может из из облака) возможно резко сократится, но процессор на сжатие и разжатие мы пожрем. В зависимости от того, что мы хотим, это может быть как плюсом, так и минусом.


  1. dolfinus
    23.01.2023 22:13
    +1

    каждая строка представляет собой отдельный json-файл

    Эм, что?


    1. NewTechAudit Автор
      25.01.2023 12:16

      Опечатался, прошу прощение. Смысл в том, что каждый создаваемый spark`ом файл состоит из строк формата:

      {“json_col”: value, ….}

      {“json_col”: value, ….}

      ….


  1. mishamota
    24.01.2023 09:30
    -2

    1. Вы используете не совсем однозначный термин "витрина данных". Что представляет из себя обозначенная "витрина" на хадупе?

    2. Авро уже сам по себе бинарник и у него по определению объем будет заметно меньше чем текстовое представление.


  1. EvgenyVilkov
    24.01.2023 11:46
    +1

    В сравнении есть один очень существенный недостаток - вы не пишете чем будете читать эта данные. Parquet содержит блочные и даже страничные индексы. Движок который их понимает работает на порядок быстрее того который такими данными оперировать не может. Кроме того, Parquet + Iceberg улетает в космос, потому что кроме storage индексов начинает работать скрытое bucket секционирование.

    Ничего не сказано про компрессию. Какая использовалась при сравнении и использовалась ли вообще? Parquet + ZStandard = лучшая компрессия и скорость чтение.

    Слабый материал, увы.


  1. brake
    24.01.2023 19:58
    +1

    Сравнение поверхностное, вместо измерения скорости было бы лучше раскрыть разницу между orc и parquet