Как стать автором
Поиск
Написать публикацию
Обновить
22.58
Data Sapience
Российский вендор, разработчик ИТ-решений

Проблема маленьких файлов. Оценка замедления S3 и проблем HDFS и Greenplum при работе c ними

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров1.9K

Не так давно в блоге компании Arenadata был опубликован материал тестирования поведения различных распределенных файловых систем при работе с маленькими файлами (~2 Мб). Краткий вывод: по результатам проверки оказалось, что лучше всего с задачей маленьких файлов справляется старый-добрый HDFS, деградируя в 1.5 раза, S3 на базе minIO не тянет, замедляясь в 8 раз, S3 API над Ozone деградирует в 4 раза, а наиболее предпочтительной системой в при работе с мелкими файлами, по утверждению коллег, является Greenplum, в том числе для компаний «экзабайтного клуба». Коллеги также выполнили огромную работу по поиску «Теоретических подтверждений неожиданных показателей».  

Результаты тестирования в части S3 minIO показались нашей команде неубедительными, и мы предположили, что они могут быть связаны с:

  • недостаточным практическим опытом эксплуатации SQL compute over S3 и S3 в целом;

  • особенностями сборок дистрибутивов;

  • отсутствием опыта работы с кластерами minIO. В частности в высоконагруженном продуктивном окружении на 200+ Тб сжатых колоночных данных Iceberg/parquet, особенно в сценариях, где проблема маленьких файлов быстро становится актуальной.

Мы благодарны коллегам за идею и вдохновение провести аналогичное тестирование. Давайте разбираться.

Описание окружения

Тестовая среда была собрана в виртуальном окружении Яндекс.Облака.

Конфигурация виртуальных узлов под minIO:

Число узлов

4

vCores

8

RAM, Gb

32

Дисковая подсистема

4 сетевых SSD-диска размером 1024 каждый (суммарно на кластер – 16 сетевых дисков)

В качестве SQL-движка мы тоже использовали Impala – 4.5_2025.04 в составе релиза платформы Data Ocean Nova 2025.04.

Конфигурация Kubernetes узлов Impala:

Число узлов

4

vCores

32

RAM, Gb

252

Локальное дата-кэширование SQL-движка на worker-узлах было полностью отключено, чтобы во время тестирования чтение шло только с S3.

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

Описание эксперимента

Тестирование тоже проводилось по методике TPC-DS. Но мы решили не ограничиваться полумерами в виде последовательного запуска 99 SQL-запросов, а проводили замеры в 4 одновременные сессии TPC-DS с суммарным количеством SQL-запросов 396. Все строго требованиям методике при конкурентном запуске. Нашей целью было создать максимально некомфортный с точки зрения нагрузки на файловое хранилище сценарий. Буквально было желание, чтобы «плохой  minIO» захлебнулся в запросах от движка на листинг и чтение маленьких файлов, и глубокое погружение в «теоретическое исследование неожиданных показателей» оригинальной статьи было не зря.

Коллеги из Arenadata посчитали, что размер файла в 2,3 Мб является достаточно маленьким и характерным для подхода Change Data Capture, особенно если работаешь с HDFS. Но мы решили падать на самое дно и постепенно с него выбираться, увеличивая размер. Наши данные были подготовлены через Spark в формате parquet в 14-ти схемах, в каждой из которых был свой целевой размер parquet-файла, который варьировался от 256 Кб до 128 Мб. 

Номер эксперимента

Объем файлов, Мб

Число файлов в S3

1

128

248

2

96

322

3

64

463

4

48

612

5

32

956

6

24

1339

7

16

1878

8

12

3338

9

8

7037

10

4

11733

11

2

28107

12

1

46836

13

0,5

102178

14

0,25

140102

В самом неблагоприятном сценарии общее число файлов тестового набора данных превышало число файлов из теста Arenadata в 10 раз! – 140 тыс против 14 тыс.

Как видите, мы намеренно не стали ограничиваться крайними значениями самого маленького и самого большого, чтобы показать динамику «падения» производительности в зависимости от размера.

Результаты

На диаграмме представлены результаты прохождения теста каждым из 4-х независимых потоков TPC-DS, запускаемых одновременно:

  • На оси Y – время работы в секундах c шагом 125 секунд;

  • На оси X (логарифмическая шкала) – размер файла в Мб.

Для упрощения анализа выведем на диаграмму общее время прохождение теста, которое будет равно времени самого медленного потока в группе.

График. Время работы TPC-DS 4 потока в зависимости от размера файлов:

  • На оси Y – время работы в секундах c шагом 125 секунд;

  • На оси X (логарифмическая шкала) – размер файла в Мб.

Представим результаты в табличном виде.

Само по себе время прохождения теста в абсолютном значении нам тут неинтересно. Главное, что нам нужно понять, –  длительность относительно времени работы на целевом в нашем тесте размере файла в 128 Мб.

Выводы

  • На реперной точке 2 Мб – результат в 1.8х раз хуже (вспоминаем результаты тестов коллег, где файлов в 10 раз меньше: HDFS - 1.5х, S3 API Ozone 4х, minIO - 8х);

  • В проведенном эксперименте значительная (больше чем 1.8 раза) деградация производительности начиналась только при размере файлов меньше 4 Мб; 

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

    • Наш best practice для максимальной производительности: метрика среднего размера файла на всем кластере (весь объем / количество всех файлов) должна быть не ниже 30-40 Мб;

  • Максимальная деградация в 2.5 раза относительно размера файла в 128 Мб была зафиксирована при размере 256 Кб;

  • Несмотря на то, что интенсивность обращения к объектному хранилищу в несколько раз выше, чем у эксперимента коллег, максимальное снижение производительности в 2.5 раза при 256 Кб и 1.8 раза при 2 Мб в нашем случае далеко от заявленных коллегами 10+ раз (до 1000%+). При этом во время нашего эксперимента SQL-движок создавал на объектное хранилище нагрузку в 40 раз больше, чем в тесте коллег.

Для закрепления полученных результатов и усиления своей точки зрения не станем прибегать к поиску ссылок в интернете и компиляции pdf-документов в стиле аналитических записок. Вместо этого предлагаем пригласить нашу команду на очное тестирование, чтобы убедиться и сделать выводы на личном примере.

Существует ли вообще проблема маленьких файлов?

Конечно, существует! Для своевременного предотвращения деградации в решениях класса Lakehouse или Data Lake важно превентивно бороться с этой проблемой и мониторить состояния объекта, оценивая средний размер файла. Особенно сильно проблема маленьких файлов проявляется при real-time загрузке данных. Для таких сценариев процесс компактирования и обслуживания настраивается до начала подключения потока данных.

В пределе эта проблема не только влияет на производительность, но и в случае ставших мейнстримом Iceberg-таблиц «ломает» их из-за приведения в необслуживаемое состояние. В итоге в запущенном случае потребуется неадекватно большое количество ресурсов кластера, чтобы сделать компакцию таблицы. Запросы на чтение значительно замедляются по времени на уровне SQL-compute, когда необходимо прочитать в 100х+ больше файлов и метаданных iceberg.

В нашей платформе Data Ocean Nova есть встроенный managed-сервис обслуживания Iceberg с графическим интерфейсом, который является частью платформы и избавляет пользователей от создания самостоятельных проектных решений.

А как же HDFS?

C практической точки зрения HDFS namenode ограничена 200М-300М файлов, независимо от их размера. Хотя, при этом существует абсолютный максимальный лимит fsimage inode 2^31 (2 млрд). 

Почему так происходит? Причин несколько:

  • Утилизация памяти на heap namenode (старая добрая JVM) и GC больших heap-ов самих по себе. В среднем на 150 млн объектов heap-область будет порядка 100 ГБ;

  •  «Снапшутирование» HDFS – часто используемый подход в крупных промышленных кластерах, как минимум для задачи репликации и изолирования изменений без использования открытых табличных форматов. Если включить его, при 5 снапшотах на объект и всего лишь 5% изменений в каждом (обе цифры – очень консервативная оценка, на моей практике эксплуатации 5 Пт кластера HDFS на «снапшутирование» уходит до 20% объема при 3х снапшотах на объект) размер heap будет умножен на 1.3: для 150 млн объектов – 130 ГБ. Если продолжить эксперимент, то для 300 млн объектов – 260 ГБ;

  • Этот расчет характерен для файловых колоночных форматов parquet / orc. А теперь давайте представим, что у нас табличный формат Iceberg. Что он добавит? Появляется много метафайлов. Их количество сопоставимо с количеством дата-файлов + delete-файлы накопленных изменений + большая фрагментация самих дата-файлов + версионирование Iceberg. Как следствие, количество «полезных» файлов  (именно файлов данных актуального снапшота iceberg) будет уже практически 30-50М на namenode, так как остальную «квоту» займет сопутствующее окружение Iceberg. Не стоит забывать и о непрерывности сервиса: время старта namenode на практике ~30 минут.

Как решают проблемы мелких файлов в HDFS? В первую очередь, следят за средним размером файлов и укрупняют их. В случае Iceberg, как и в S3, помогают обслуживание и компакция Iceberg-таблиц и манифестов. Если же количество файлов с учетом регламентного обслуживания все равно превышает практический предел, то используют HDFS-федерацию, но сложность решения и инсталляции с федерацией значительно увеличивается, потому что:

  • Каждая namenode требует своего HA;

  • Namespace изолированы: нельзя сделать move для перемещения, только копирование;

  • Балансировка нагрузки по namenode становится ручной: нет автоматического распределения, планирование размещения данных выполняется вручную, ведь дата-узлы могут быть перегружены одним namespace;

  • Операционная сложность: бэкап/рестор/репликации, HA-конфигурации, обновление версий, задание квот (per namespace), разграничение ресурсов и требования к компетенциям администраторов усложняются.

Все эти проблемы и являлись мотиватором миграции HDFS-федераций в native S3 решения (Ceph, minIO, public cloud S3), а также поводом создания самого проекта Apache Ozone.

А как же Greenplum?

Сперва – немного известных фактов об устройстве postgres / GP:

  1. В postgres каждая секция каждой таблицы имеет свой набор файлов данных

    1. Сами дата-файлы со страничной организацией внутри и разбивкой по 1 Гб (каждый следующий гигабайт – новый файл);

    2. Служебные файлы: free space map (FSM), visibility map (VM), _init (для unlogged-таблиц);

    3. Индекс-файлы (если есть индексы);

  2. GP состоит из сегментов. На одном физическом узле работает от 4 до 8 сегментов, каждый сегмент – инстанс postgres + один активный общий мастер и его standby postgres;

  3. Главный тип таблиц в GP для аналитики / DWH – Append Only Column Oriented таблицы (АОСО): на каждом сегменте на каждую секцию на каждую колонку – свой файл данных с логикой пункта 1 по составу и по разбивке. Помимо колонок видимых есть еще технические колонки: 33 шт на AOCO, на них по одному файлу на секцию;

  4. Второй ключевой тип таблиц – AOT Row – Append Only Row Oriented – со сжатием, но строчным хранением, существенно менее оптимален для HTAP- /OLAP-нагрузки для больших кластеров (меньше сжатие, нет возможности колоночных чтений). Но логика ближе к postgres в плане файлов: создается по одному дата-файлу на 1 Гб данных на одну секцию;

  5. Все крупные таблицы должны быть распределены близко к равномерной дистрибьюции по всем сегментам (которые по сути и являются единицами параллелизма). Соответственно, каждый сегмент имеет свой кусочек каждой секции со всеми ее файлами для параллельной обработки (storage и compute жестко соединены + симметричный mpp-процессинг);

  6. Каталог pg_catalog реплицируется на каждый сегмент;

Там, где далее по тексту используется термин «количество секций» кластера (или еще корректнее «количество объектов в терминах pg_catalog»), имеется в виду сумма количества всех таблиц без секций + сумма количества всех секций всех секционированных таблиц на всем кластере.

Проведем реальный практический эксперимент деградации работы pg_catalog 

  1. Сайзинг стенда: 

    1. On-prem выделенное оборудование, 4 сегмент-хоста с характеристиками 64 CPU Cores, 1 Tb RAM, 8 сегментов на хост;

    2. Дисковая подсистема сегмент-хоста состоит из 2х массивов RAID-10 по 4х Enterprise NVMe PCIE gen4 х4 lines диска каждый (всего 8 дисков соответственно);

    3. Локальная сеть 100 Гбс и дополнительный 100 Гбс бондинг для резервирования;  

(!)Все цифры, полученные в эксперименте ниже, являются максимально производительными ввиду использования NVMe дисковой подсистемы и пропускной способности сети в 100 Гбс;

  1. Сделаем 331 AOCO таблицу. В каждой таблице – 100 колонок (с учетом технических колонок – 133). В сумме по всем таблицам – 5,4 млн секций, 32 млн мелких файлов на каждом сегменте (!). В таблицах еще 0 строк, не создались файлы на каждую колонку. При первой операции INSERT хотя бы одной строки – там, куда попадает строка, будет +100 файлов на сегменте при всех заполненных секциях, не менее чем стократно умноженное количество секций каждой таблицы на каждом сегменте или примерно х15 файлов для этого кейса. Появление файлов на колонках не повлияет далее на работу каталога, а вот работу SELECT/DML и файловую систему физического узла изменит кардинально. Но мы тестируем пока что эффекты работы с каталогом, потому что кластер, увы, останавливается уже тут;

  2. Системный каталог занимает 12 Тб до vacuum (картинка ниже) и 9 Тб после, в том числе потому что он replicated на всех сегментах. Дата-файлы с 0 фактических строк заняли 11 Тб – это уже реальная big data на пустом, буквально, месте!

  1. Замеры и выводы при 0 строках в самих AOCO таблицах данных:

    1. На 400к секций (взяли такую точку как наиболее релевантную крупным кластерам) запросы к каталогу уже деградируют до 5 секунд;

    2. На 5,4 М секций – деградация уже больше минуты (75 секунд, а после vacuum full системного каталога – 55 секунд);

    3. Динамика деградации линейна: на каждые 10к пустых секций приходятся плюсом дополнительные 100 Мс;

    4. При 5.4 М секций: неконкурентный DDL в одну сессию (CREATE новой пустой дополнительной таблицы на 1000 секций) – 10 секунд, DROP пустой таблицы - 10 минут!

    5. Конкурентные операции dml/ddl на 80-100 сессий над каталогом при 400К секций полностью останавливают работу кластера!

  1. С AOT Row все получше: 1 млн секций AOT Row и 500 одновременных запросов на указанном типе дисков при правильном тюнинге – это порог, когда кластер пока еще не деградирует в части обращений к pg_catalog.

С практической точки зрения в промышленной эксплуатации важно количество конкурентных сессий. Да и сам оптимизатор на каждом запросе тоже пользуется каталогом.

Получается, что практические архитектурные ограничение Greenplum следующие:  

  • AOCO – количество секций в рамках одного мощного физического кластера, собранного с NVMe-дисками: ~300К на кластер и 100 конкурентных сессий с минимальной нагрузкой – предел, независимо от аппаратного обеспечения для одного физического кластера GP;

  • AOT Row – 1 млн секций и 500 сессий для NVMe-дисков и достаточного кол-ва RAM.

Существенное замедление операций SELECT/DML/DDL, простой кластера на vacuum-операции каталога и дата-файлов, скорости и вообще выполнения операций обслуживания -backup\restore и репликации называется проблемой «bloat каталога GP».

Увы, как бы ни хотелось, Greenplum не переваривает большое количество мелких файлов из-за своей архитектуры, просто залипая на первичном шаге –  листинге объектов pg_catalog’а.

Подводя итоги

С одной стороны, был проведен экстремальный эксперимент на 5,4 млн секций в Greenplum, что, конечно, является фантастикой. Но и не мы утверждаем, что GP масштабируется на ПЕТАБАЙТЫ сжатых данных с значимой конкурентной нагрузкой.

C другой стороны, крупные кластеры GP на пару сотен Тб в реальной жизни имеют те самые 100-400 тысяч секций AOCO, что, как мы видим, и является близким к технологическим пределам одного кластера. 

За эксперимент и материал с Greenplum спасибо Марку Лебедеву и его команде! Недавно, кстати, Марк опубликовал большой качественный материал по сравнению производительности GP6, GP7 и Cloudberry. Рекомендую к прочтению.

В заключение, по традиции, анонсирую следующую статью на тему распределенных файловых хранилищ для аналитики «Сравнительное тестирование производительности: Apache Ozone vs S3 minIO». Также на подходе очередная итерация нагрузочного тестирования по методике TPC-DS со StarRocks и Spark. Обещаю, будет, как всегда, интересно и познавательно.

Справка о компании: 

Data Sapience — российский вендор, разработчик ИТ-решений для бизнеса. Разработкой продуктов занимается команда с 15-летним опытом работы в консалтинге и с ведущими вендорами. Продукты организации состоят в Реестре отечественного ПО: CM Ocean – платформа CVM-маркетинга; TALYS Ocean – платформа интегрированного управления рисками; Kolmogorov AI — платформа аналитики данных, машинного обучения и MLOps; Data Ocean Governance – платформа управления данными организации; Data Ocean – универсальная Lakehouse-платформа данных. 

Следите за обновлениями и подписывайтесь на телеграм-канал Data Sapience.

Теги:
Хабы:
+1
Комментарии7

Публикации

Информация

Сайт
datasapience.ru
Дата регистрации
Численность
Неизвестно
Местоположение
Россия
Представитель
Елизавета Рощина