В Hadoop входят продукты, которые могут работать с файлами разных форматов. Я неоднократно искал, читал и думал над тем — какой же формат лучше. Относительно случайно столкнувшись с форматом ORC, заинтересовался, почитал (и даже чуть покодил) и вот что понял — сравнивать форматы как таковые некорректно. Точнее, их обычно сравнивают, на мой взгляд, некорректным образом. Собственно, статья об этом, а также о формате Apache ORC (в техническом плане) и предоставляемых им возможностях.
Начну с вопроса: каким может быть размер реляционной таблицы (в байтах и очень примерно), состоящей из 10 тысяч строк (по два целых поля в строке)? Обычно здесь ставят кат, а ответ помещают под катом — я отвечу здесь: 628 байт. А детали и историю перенесу под кат.
Как все началось: собрал я библиотеку для работы с Apache ORC (см. заглавную страницу проекта — https://orc.apache.org) и скомпилировал их же пример про запись в ORC (что голову ломать — начинаем с того, что работает), это в нем было 2 поля и 10 тысяч строк. Запустил — получил orc-файл, поскольку делал я это где-то не в офисе — на всякий случай переписал библиотеку и файл на флэшку (торопился — не стал смотреть размер, думаю, флэшка-то выдержит).
Но как-то быстро переписалось… Посмотрел на размер — 628 байт. Подумал — ошибка, сел и начал разбираться. Запустил утилиту для просмотра ORC из той же собранной библиотеки — содержимое файла показывает, все честно — 10 тысяч строк. Вот после этого я и призадумался — как может 10 тысяч строк влезть в 628 байт (я к тому времени уже немного знал про ORC и понимал, что там есть еще и метаданные — формат же самодостаточный). Разобрался, делюсь.
О формате ORC
Не буду повторять здесь общие слова про формат — см. ссылку выше, там хорошо написано. Сфокусируюсь на двух прилагательных в превосходной форме с картинки выше (картинка взята с заглавной страницы проекта): давайте попробуем разобраться — почему ORC "самый быстрый" и "самый компактный".
Скорость
Скорость бывает разная, применительно к данным — как минимум скорость чтения или записи (можно углубляться и дальше, но давайте пока остановимся). Поскольку в лозунге выше явно упоминается Hadoop, то рассматривать будем прежде всего скорость чтения.
Процитирую еще немного из документации ORC:
It is optimized for large streaming reads, but with integrated support for finding required rows quickly. Storing data in a columnar format lets the reader read, decompress, and process only the values that are required for the current query.
Чуть переведу:
- формат оптимизирован под потокое чтение больших объемов
- при этом содержит поддержку для быстрого поиска необходимых строк
- позволяет производить чтение только тех данных, которые нужно
Размер
Тут цитаты не нашлось, скажу своими словами
- формат оптимальным образом хранит метаинформацию
- обеспечивается баланс между скоростью потокового чтения и компактностью хранения
- встроена поддержка для максимально компактного хранения значений колонок
Предоставление возможностей
Хочу обратить Ваше внимание на формулировки из цитат выше: "оптимизирован под...", "содержит поддержку...", "позволяет производить чтение..." — формат файла, как язык программирования, является средством (в данном случае, обеспечения эффективного хранения и доступа к данным). Будет ли хранение и доступ к данным действительно эффективными зависит не только от средства, но и от того, кто и как этим средством пользуется.
Давайте посмотрим — какие возможности для обеспечения скорости и компактности предоставляет формат.
Колончатое хранение и страйпы
Данные в ORC хранятся в виде колонок, прежде всего это влияет на размер. Для обеспечения скорости потокового чтения файл разбит на так называемые "страйпы" (stripes), каждый страйп является самодостаточным, т.е. может быть прочитан отдельно (а, следовательно, параллельно). Из-за страйпов размер файла увеличится (не уникальные значения колонок будут храниться несколько раз — в тех страйпах, где такие значения встречаются) — тот самый баланс "скорость — размер" (он же компромисс).
Индексы
Формат ORC подразумевает индексы, позволяющие определить — содержит ли страйп (а точнее — части страйпа по 10 тысяч строк, так называемые "row group") искомые данные или нет. Индексы строятся по каждой из колонок. Это влияет на скорость чтения, увеличивая размер. При потоковом чтении индексы, кстати, можно и не читать.
Сжатие
Все метаданные хранятся в сжатом виде, а это
- статистическая и описательная информация (формат позволяет воссоздать таблицу, которая в нем хранится, включая названия и типы полей)
- индексы
- информация о разбиении (на страйпы и потоки)
(ниже мы увидим, что метаданные составляют существенную часть файла)
Значения колонок также хранятся в сжатом виде. При этом обеспечивается возможность чтения и распаковки только того блока данных, который нужен (т.е. сжимается не файл и не страйп целиком). Сжатие влияет и на размер, и на скорость чтения.
Кодирование
Значения колонок — а файл хранит именно эти значения — хранятся в кодированном виде (encoded). В текущей версии формата (ORC v1) для целых чисел, например, доступно 4 варианта кодирования. При этом кодируется не вся колонка целиком, кодируются части колонки, каждая часть может быть закодирована оптимальным для этой части образом (такие части в спецификации называют "run"-ом). Таким образом достигается минимизация суммарной длины хранимых данных. Опять же влияние на размер и на скорость.
Посмотрим ORC файл
Давайте очень кратко посмотрим — что внутри ORC файла (того самого — 628 байт). Для тех, кого не очень интересуют технические детали — пролистайте до следующего раздела (про сравнение форматов).
Вот так определялась наша таблица в примере записи в ORC:
Метаданные
Информация о длинах (я привожу скриншоты jupyter notebook, думаю, достаточно понятно)
Что мы здесь видим:
- в "хвостовике" (а это — Postscript+Footer+Metadata) всего 1+23+115+50=189 байт
- в единственном страйпе всего 3+436=439 байт, итого 628 байт
- страйп содержит индекс (73 байта), данные (276 байт), футер (87 байт)
Обратим внимание здесь на соотношение объема данных и метаданных (276 к 352 байт). Но эти 276 байт данных — тоже не только данные, данные содержат немного "лишнего" (здесь для краткости не привожу скриншоты — с ними длинно получается, обойдусь только своими комментариями), что входит в данные:
- потоки PRESENT для каждой колонки, их три (включая общую псевдо-колонку struct) — по 20 байт на каждую, всего 60 байт
- потоки с данными (тут псевдо-колонка не представлена) — 103 и 113 байт (колонки "x" и "y" соответственно)
Потоки PRESENT — это битовые строки, позволяющие понять — где в колонках стоят NULL. Для нашего примера их присутствие выглядит странно (в статистике нашего файла явно написано, что NULL-ов в данных нет — зачем тогда включать PRESENT? Похоже на недоработку...)
Итого собственно данные занимают 216 байт, метаданные — 352.
Также из метаданных видно, что обе колонки закодированы с помощью DIRECT_V2 метода (для целых он допускает 4 вида представления, за деталями отсылаю к спецификации — она есть на сайте проекта).
Данные
Посмотрим (опять без скриншотов для краткости), как же 10 тысяч чисел поместилось в 103 байта (для колонки "x"):
- используется delta кодировка, у которой параметрами являются начальное значение и шаг (немного упростил для краткости)
- у нас шаг всегда 1, начальное значение для первого run-а — 0, далее — 511, 1022 и т.д.
- run (набор данных, закодированных единым образом) в нашем случае содержит 511 значений (максимально возможное значение для delta кодировки)
- длина каждого run-а в файле — от 4 до 6 байт (длина run-а растет за счет того, что начальное значение представлено с использованием zigzag-а)
- итого для колонки "x" мы получаем в файле 20 run-ов общей длиной 103 байта (я проверял — все сходится)
Завершая обзор представления нашей простой таблицы в файле скажу, что индексы в этом примере вырождены — указывают на начало потока данных. С индексами я разберусь на реальных примерах, возможно, опишу в отдельной статье.
Для интересующихся: по ссылке можно найти jupyter notebook, в котором я "разбирался" во внутренностях формата. Можно им воспользоваться и повторить (ORC файл там тоже приложен).
Уверен, что многие читатели "потерялись" — да, формат ORC не является простым (как в плане понимания деталей, так и в плане использования предоставляемых возможностей).
О сравнении форматов
Теперь перейдем к главному — некорректности сравнения форматов.
Как часто сравнивают форматы: давайте сравним размер файлов в формате А и Б, скорость чтения (разных видов чтения — случайное, потоковое и т.п.) в формате А и Б. Сравнили, сделали вывод — формат А лучше формата Б.
На примере последнего из перечисленных выше средств обеспечения компактности (кодирования): можно ли в ORC закодировать данные оптимальным образом? Да, возможности есть — см. выше. Но точно также можно этого и не делать! Это зависит от "писателя" (writer в терминологии ORC): в приведенном выше примере писатель это смог сделать. Но он мог бы просто записать 2 раза по 10 тысяч чисел и это тоже было бы корректно с точки зрения формата. Сравнивая форматы "на размер" мы сравниваем не только и не столько форматы, сколько алгоритмическое качество использующих эти форматы прикладных систем.
Кто есть "писатель" в Hadoop? Их много — например, Hive, который создает таблицу, хранящую свои данные в файлах в формате ORC. Сравнивая, к примеру, ORC с Parquet-ом в Hadoop, мы на самом деле оцениваем качество реализации алгоритма преобразования данных, реализованного в Hive. Мы не сравниваем форматы (как таковые).
Важная особенность Hadoop
В классическом реляционном мире у нас не было никакой возможности повлиять на размер таблицы в Oracle — она как-то хранилась и только Oracle знал — как. В Hadoop ситуация чуть другая: мы можем посмотреть — как хранится та или иная таблица (насколько хорошо у Hive, например, получилось ее "закодировать"). И, если мы видим, что это можно улучшить, — у нас есть для этого реальная возможность: создать свой более оптимальный ORC файл и дать его Hive-у в качестве внешней таблицы.
Сравним ORC и QVD
Я недавно описал формат QVD, который активно используется QlikVew/QlikSense. Давайте для иллюстрации очень кратко сравим эти два формата с точки зрения возможностей, которые они предоставляют для достижения максимальной скорости чтения и минимизации размера. Возможности ORC описаны выше, что у QVD:
Колончатое хранение
QVD может считаться "колончатым" форматом, в нем нет дублирования значений колонок — уникальные значения хранятся один раз. НО он не допускает параллельной обработки — сначала необходимо полностью считать значения всех колонок, потом можно параллельно читать строки.
А дублирование есть на уровне строк — строки хранят повторяющиеся значения индекса в таблице символов.
Сжатие
Не сталкивался со сжатыми QVD файлами — не довелось — тэг такой в метаданных есть, возможно, каждая из частей, про которую в метаданных есть смещение и длина (а это — каждая таблица символов и вся таблица строк), может быть сжата. В этом случае параллельное чтение строк — "давай до свиданья"...
Индексы
В QVD файле нет возможности понять — какую его часть необходимо читать. На практике приходится побайтово разбирать таблицу символов (каждую!), не очень эффективный способ...
Кодирование
Кодирование в QVD не применяется, можно провести аналогию битового индекса в таблице строк с кодированием, но эта аналогия "компенсируется" дублированием чисел строками в таблицах символов (детали — см. в статье, кратко — значение колонки часто представлено числом И строкой).
Вывод по этому краткому сравнению у меня лично получился такой — формат QVD практически не содержит возможностей, позволяющих компактно хранить и быстро читать содержащиеся в файлах этого формата данные.
(Прозвучало как-то обидно для QVD, чуть дополню — формат был создан очень давно, используется только QlikView/QlikSense, а они "хранят" все данные в памяти. Я думаю, что QVD файл просто читается весь "как есть" в память, а далее эти замечательные во всех отношениях BI продукты очень быстро работают с этим представлением — тут они мастера...)
Вместо заключения
Покритиковал и ничего пока не предложил… — предлагаю.
Мне представляется, что сравнивать форматы нужно не на примере их конкретной реализации, сравнивать форматы нужно с точки зрения входящих в них инструментальных средств и возможности использовать эти средства для решения наших конкретных задач. Скорость работы работы процессоров постоянно растет, сейчас мы можем себе позволить практически любые алгоритмы преобразования данных после того, как они считаны — все равно чтение с диска будет медленнее. Именно поэтому "выразительные средства" форматов важны.
Выше я кратко перечислил интересные, на мой взгляд, возможности формата ORC. У меня еще нет статистики по тому, как обстоит дело на практике (какие из этих возможностей и в какой мере используются Hive-ом, например). Когда появится — напишу. В ближайших планах сделать подобный обзор другого популярного формата хранения — Parquet.
Ну и — совсем в заключение — в современном мире очень много информации, к сожалению, часть этой информации является слишком поверхностной. Не будем поддаваться, будем смотреть в суть.