В этой статье для обучения дата-инженеров и администраторов кластера Apache HBase разберем, почему региональные сервера могут работать некорректно при высокой нагрузке и при чем здесь SCR-конфигурация файловой системы Hadoop. Что такое Short-Circuit Read в HDFS и почему оно может снижать скорость потокового чтения в приложениях Spark Streaming.
Постановка задачи: проблема медленного чтения потокового приложения
Современные информационные платформы состоят из множества компонентов, каждый из которых может отказать, повлияв на общую производительность всей системы. При этом не всегда можно сразу понять, в чем причина глобального сбоя или изменения важных метрик. Например, низкая скорость потоковой обработки приложения Spark Streaming не обязательно связана с неравномерным распределением данных или неоптимальным распределением вычислительных ресурсов, о чем мы писали здесь. Если потоковое приложение работает в рамках распределенного конвейера, проблема может быть в предшествующих компонентах, например, источниках данных.
Именно такая ситуация возникла в одной компании, где рабочие нагрузки в многопользовательском кластере включали большие пакетные задания полного сканирования таблиц Apache Spark, преобразование и запись результатов обратно в исходную таблицу, а также чтение, вставку и обновление нескольких потоковых приложений Kafka. Сбои часто случались при резервном копировании многоэтапного, чувствительного к задержкам приложения Spark Streaming, которое считывало данные из Apache HBase — популярной колоночной NoSQL-СУБД поверх HDFS. HBase обеспечивает возможности BigTable для Hadoop, реализуя отказоустойчивый способ хранения больших объёмов разреженных данных и случайный доступ к ним в реальном времени вместе с удобством пакетной обработки.
Распределение данных по разным физическим узлам кластера HBase обеспечивает механизм регионирования, когда строки таблиц, соответствующие определенному диапазону идущих подряд первичных ключей, автоматически группируются по горизонтали в единый диапазон – регион. Один или несколько регионов обслуживаются региональным сервером (RegionServer). А за распределение регионов по региональным серверам отвечает главный узел в кластере, MasterServer. Как рассчитать оптимальное количество регионов в таблице и их размер, читайте в нашей новой статье.
В рассматриваемом кластере Apache HBase некоторые региональные серверы прерывали работу при высокой нагрузке и иногда зависали без сбоев, временно удерживая свои данные в автономном режиме. Чтобы понять причину такого поведения, были проанализированы логи. В HBase восстановление после сбоя перемещает регионы, что приводит к удаленному чтению всех задач Spark и MapReduce, которые уже были запланированы. Удаленные задачи выполняются намного дольше, что резко замедляет обработку данных.
Анализ логов показал, что из подсистемы журнала упреждающей записи (WAL, Write Ahead Log) было выдано исключение ввода-вывода IOException, при этом система WAL постоянно обноваляла сам лог-файл, очищая и закрывая его, чтобы запустить новый. Это критически важный процесс для обеспечения надежности данных, и сбой здесь привел к общему прерыванию процессов (ABORT). Такое поведение системы WAL было спровоцирована инцидентами IOException, о чем сообщалось в логе отказавшего регионального сервера, когда процессу не удалось синхронизировать редактирование с WAL, размещенным на HDFS. Сообщив клиенту об ошибке, HBase пытается откатить WAL, с которым не удалось выполнить синхронизацию. Это действие сбрасывает подсистему WAL, заменяя экземпляр и связанное с ним состояние клиента HDFS. Во время обновления WAL региональный сервер сообщает об исключении ввода-вывода, из-за которого он прерывает свою работу, а узел данных HDFS (DataNode) сбрасывает соединение.
Просмотр локального лога узла данных HDFS выявил причину случайного сбоя синхронизации и сброса соединения, которая состояла в исключении NullPointerException и сбое процесса записи лога. Совокупность этих ошибок приводила к отказу процесса регионального сервера HBase. При этом иногда отказывал не просто локальный одноранговый узел данных HDFS, а один из удаленных узлов, участвующих в разветвлении WAL. Чтобы избежать такой ситуации, рекомендуется использовать DFSOutputStream только для ведения журнала WAL.
Возвращаясь к рассматриваемому кейсу, отметим, что узлы данных падали со скоростью, которая намного превышала частоту сбоев региональных серверов, хотя они почти всегда восстанавливались и продолжали свою работу, когда сбой разрывал установленное соединение во время обновления WAL. В чем была корневая причина этой проблемы, рассмотрим далее.
Код курса
NOSQL
Ближайшая дата курса
по запросу
Продолжительность
ак.часов
Стоимость обучения
0 руб.
Сбои HBase из-за настроек HDFS
Вышеупомянутое исключение NullPointerException в #isEventsHighKilled — это старая ошибка JVM, связанная с проектом Netty – асинхронной клиент-серверная среды ввода-вывода для разработки сетевых приложений Java с серверами сокетов TCP и UDP. В поиске способов устранения этой ошибки было обнаружено, что узлы данных HDFS работали с дескриптором файла процесса ulimit, равного 16 КБ. Хотя обычно узлы данных в этом кластере работают с активными файловыми дескрипторами от 2 до 4 тысяч байт. Что-то приводило к тому, что число файловых дескрипторов превышало максимально допустимое значение в 16 тыс. После обновления JDK с 8-ой до 11-ой версии дескриптор файла ulimit был увеличен, что устранило хронический перезапуск процессов узлов данных и избавило региональные сервера HBase от постоянных сбоев.
Хотя это исправление улучшило общее состояние кластера, потоковое приложение Spark Streaming все еще выполняло резервное копирование, что снижало скорость его работы. Внутри приложения на определенном этапе задания потоковой передачи в пользовательском интерфейсе Spark можно было посмотреть представление по частоте обращений к региональным серверам HBase. Анализ этих данных показал, что причиной резервного копирования Spark-приложения был региональный сервер HBase, который постоянно работал с наивысшей частотой обращений.
Примечательно, что этот региональный сервер демонстрировал высокий процент обращений, но также страдал от высокой задержки ответа, а его RPC-очередь была сильно зарезервирована. Процессы RegionServer и DataNode сообщили о слишком большом числе файловых дескрипторов, превышающем 100 КБ. Анализ дампов потоков для большинства обработчиков, читающих в этом состоянии, показал, что операции чтения ожидали выделения слота в сегменте общей памяти SCR.
Таким образом, главной причиной сбоя в многокомпонентной системе хранения и аналитики больших данных были конфигурационные настройки HDFS, связанные с операциями чтения.
В HDFS операции чтения обычно проходят через узлы данных: когда клиент запрашивает DataNode прочитать файл, узел данных считывает его с диска и отправляет клиенту через сокет TCP. Это называется «короткое замыкание чтения» (Short-Circuit Local Reads), которое позволяет клиенту читать файл напрямую в обход узла данных. Это возможно только в тех случаях, когда клиент находится рядом с данными. Чтение с коротким замыканием значительно повышает производительность многих приложений. Чтобы настроить SCR, нужно включить нативную Hadoop-библиотеку libhadoop.so.
Чтения с коротким замыканием используют сокет домена UNIX – специальный путь в файловой системе, который позволяет клиенту и узлам данных обмениваться данными. Чтобы DataNode мог создать этот путь к сокету, нужно его сперва указать. При этом ни один пользователь, кроме пользователя HDFS или root, не должен иметь возможности создать этот путь. Поэтому на практике часто используются пути в директориях /var/run или /var/lib. А сам обмен данными между клиентом и DataNode происходит через сегмент общей памяти в /dev/shm.
Примечательно, что SCR необходимо настроить как на узле данных, так и на клиенте. Пример такой конфигурации может выглядеть следующим образом:
<configuration> <property> <name>dfs.client.read.shortcircuit</name> <value>true</value> </property> <property> <name>dfs.domain.socket.path</name> <value>/var/lib/hadoop-hdfs/dn_socket</value> </property> </configuration>
Устаревшая SCR-реализация, при котором клиенты напрямую открывают блочные файлы HDFS, по-прежнему доступна для платформ, отличных от Linux. Установка значения dfs.client.use.legacy.blockreader.local в значение true дополнительно к включенному dfs.client.read.shortcircuit активирует эту функцию.
Также Hadoop-администратору необходимо установить значение конфигурации dfs.datanode.data.dir.perm равным 750 вместо 700 по умолчанию и выполнить команду изменения режимов chmod/chown для дерева каталогов в dfs.datanode.data.dir, чтобы сделать его доступным для чтения клиенту и DataNode. Однако, выполнять все эти манипуляции нужно осторожно, т.к. клиент сможет читать все блочные файлы в обход разрешения HDFS.
Поскольку устаревшее SCR небезопасно, доступ к этой функции ограничен пользователями, указанными в значении конфигурации dfs.block.local-path-access.user. Пример задания такой конфигурации может выглядеть так:
<configuration> <property> <name>dfs.client.read.shortcircuit</name> <value>true</value> </property> <property> <name>dfs.client.use.legacy.blockreader.local</name> <value>true</value> </property> <property> <name>dfs.datanode.data.dir.perm</name> <value>750</value> </property> <property> <name>dfs.block.local-path-access.user</name> <value>foo,bar</value> </property> </configuration>
Возвращаясь к рассматриваемому кейсу, напомним, что именно SCR оказалась причиной зависания потокового Spark-приложения. Подтвердить эту догадку позволило логирование на уровне TRACE в пакете org.apache.hadoop.hdfs.shortcircuit на одном из производственных региональных серверов HBase. Сбои случались только при высокой нагрузке, когда скорость ведения журнала была настолько высокой, что переполняла подсистему логирования, обрезая лог-лайны. Сопоставив имя потока и контекст с кодом SCR, был сделан вывод о постоянной перезагрузке файловых дескрипторов для одних и тех же блоков HDFS. При этом активный поток очистки кэша не выполнял с ним никакой работы, т.е. не очищал реплики.
В рассматриваемом кластере параметр конфигурации dfs.client.read.shortcircuit.streams.cache.size был установлен в 1000 при значении по умолчанию — 256. Этот параметр управляет размером SCR-кэша HDFS дескрипторов локальных блочных файлов. По умолчанию для конфигурации dfs.client.read.shortcircuit.streams.cache.expiry.ms задано значение 5 минут. Очиститель кэша работал в 4 раза чаще. При этом когда дескриптор файла SCR возвращается в кэш SCR, код проверяет размер кэша и очищает записи, если размер превышает установленный предел. Это происходило слишком часто: скорость чтения кластера была такова, что кэш достиг своего предела. Работающему очистителю кэша нечего было удалять, потому что каждый возврат элемента приводил к очистке кэша, чтобы освободить место для размещения возвращенного элемента.
Таким образом, несмотря на то, что кэш заполнен, почти каждое чтение требовало открытия дорогостоящего встроенного блока HDFS. На практике при большой нагрузке задержка 75-м процентиля выделения локального подкласса ReplicaInfo SCR блока HDFS, где размещаются дескрипторы файлов локального блока, составляет 50 мс. Если HBase-кластер имеет порядка 4-х H-файлов на регион, каждый из которых больше 256 МБ, нужно проверить размер блока HDFS. В этом кластере он был настроен на 256 МБ.
Предположим, что для обслуживания запроса на чтение региональному серверу необходимо открыть около 2-х блоков HDFS в H-файле. В среднем размер HFile больше, чем размер блока по умолчанию, поэтому HFile содержит более одного блока HDFS. При открытии HFile следует обратиться к структуре данных трейлера HFile, где хранятся его метаданные. Затем можно прочитать данные в блоке, отличном от блока структуры данных трейлера.
Таким образом, если есть примерно 2 блока HDFS на HFile и каждый регион имеет 4 HFile, то в среднем открывается примерно 8 блоков HDFS на регион. Накладные расходы на настройку SCR, в крайнем случае, могут составлять порядка 50 мс * 8 = 400 мс, что очень много.
Количество файловых дескрипторов в процессах узлов данных и региональных серверов увеличивалось при высокой нагрузке. Это происходило не только из-за того, что выделение дескриптора файла было медленным, но и из-за операции очистки дескриптора файла SCR, что исправлено в HDFS 3.3.1. А пока дескрипторы открытых файлов зависали, ожидая запуска своей очистки.
Чтобы выполнить это, было увеличено значение конфигурации dfs.client.read.shortcircuit.streams.cache.size с 1 КБ до 64 КБ. Файловые дескрипторы находились в кэше как минимум не меньше милисекунд, чем указано в конфигурации dfs.client.read.shortcircuit.streams.cache.expiry.ms. Наконец, кэш файловых дескрипторов в SCR имел достаточную емкость для кэширования активного рабочего набора и заработал.
На самом деле рассчитать необходимое значение dfs.client.read.shortcircuit.streams.cache.size можно по следующей формуле, которая позволяет оценить среднее количество блоков HDFS на одном региональном сервере:
(общее количество физических блоков в кластере HDFS)*(коэффициент репликации HDFS)/ (количество региональных серверов).
Так можно определить размер кэша SCR, чтобы он мог кэшировать все дескрипторы блочных файлов HDFS, управляемые одним RegionServer. Значение этой конфигурации dfs.client.read.shortcircuit.streams.cache.size задается в процессе RegionServer, поскольку именно он имеет экземпляр клиента HDFS и, следовательно, экземпляр кэша ShortCircuitRead. После внесения этих изменения на всех региональных серверах в кластере HBase, медленное чтение в приложении Spark Streaming было устранено.
Освойте практические навыки эксплуатации Apache HBase и других компонентов экосистемы Hadoop для построения эффективных конвейеров аналитики больших данных на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:
Источники