Привет, Хабр!
Недавно я понял, что не знаю, что такое Hadoop.
(На этом моменте становится понятно, что данная статья ориентирована на людей, которые не имеют экспертизы и реального опыта взаимодействия с продуктами экосистемы Hadoop)
Сам я являюсь разработчиком, и ежедневно взаимодействую с различными СУБД – в основном, с пресловутой PostgreSQL. Каково же было мое удивление, когда я узнал, что на проде в эту БД данные попадают не напрямую – а с какого-то Greenplum, а туда они, в свою очередь, приходят с некоего Hadoop.
В этот момент я решил узнать, чем обоснована необходимость использования этих инструментов и что они из себя представляют.
Я пишу этот материал в процессе прояснения темы для самого себя, поэтому можно относится к этой статье, как к моему краткому конспекту. Также настойчиво прошу поправлять меня в комментариях, если я где-то был неправ или недостаточно точен.
Итак, поехали.
Проблематика, которую решает Hadoop
Каждый ребенок знает, что традиционные базы данных реляционного формата – это круто. Это понятно и абсолютно очевидно. Я делаю небольшое допущение, что для решения большинства задач, которые стоят перед разработчиком, нам необходимо взаимодействовать именно с традиционными СУБД (ну типа postgres, oracle, mysql и тд и тп).
Давайте попробуем представить, что все данные, которые используются в системе, лежат именно в виде табличек. Назревает вопрос: так зачем вообще нужен Hadoop, если у нас есть какой-нибудь большой и крутой оракл/постгрес/вставьтенужное?
Казалось бы, складываем все данные в него, и всё. Дешево и сердито.
Однако, как оказалось, не дешево.
Практически любая традиционная СУБД (особенно postgres) масштабируется вертикально – а это значит, что при росте объема данных нам нужно больше CPU/RAM/SSD на одну машину (что будет очень дорого при больших объемах данных, и более того, даже с бесконечным бюджетом будет иметь физические пределы).
Кроме того, если мы хотим работать с по-настоящему огромными объемами данных, любая вышеупомянутая СУБД рано или поздно намертво встанет, если не танцевать над ней с бубном. Аналитические запросы будут отрабатывать недопустимое время, если речь идет о петабайтах данных.
Вот представим кейс: мы – крупный банк, и ежедневно обрабатываем 1 терабайт данных (цифра вполне себе реальная) операций наших клиентов, а кроме того, храним информацию о самих клиентах и их счетах.
А при обработке каждой операции мы должны подтянуть данные об отправителе, получателе, проверить все действо на законность и тд и тп.
Росфинмониторинг диктует нам правила: хранить документы и сведения по клиенту и операциям не менее 5 лет (обычно — с даты прекращения отношений с клиентом либо в случаях с операциями — от даты операции).
Ну, объемы информации, с которой нужно будет работать, можете прикинуть сами.
Разумеется, данные об операции клиента 4х летней давности статистически пригождаются крайне редко – поэтому они будут висеть мертвым грузом в нашей БД и сильно замедлять обработку всех запросов. Короче:
Горячие данные — часто и/или срочно запрашиваемые: требуют низкой задержки (миллисекунды — доли секунды) и высокой доступности. Примеры: текущие балансы, незавершённые транзакции, сессии пользователей, кэши.
Холодные данные — редко запрашиваемые, нужны для аналитики/аудита/истории; допустима большая задержка (секунды–минуты, иногда часы). Примеры: архивы транзакций за прошлые годы, старые выписки, старые логи.
Внимательный читатель скажет, что проблема хранения решается партиционированием и будет отчасти прав.
Однако существует и вторая проблема: необходимость обработки этих данных. В том случае, если мы захотим обработать данные, хранящиеся в разных партициях/на разных нодах, мы упремся в то, что «бутылочным горлышком» является процессор самой СУБД, который будет пытаться последовательно обработать все данные – предварительно достав их из распределенного хранилища. Очевидно, что на больших объемах данных это очень неэффективно по времени и ресурсам.
Вот бы можно было все эти петабайты данных просто закинуть файликами на какие-нибудь старенькие накопители на несколько серверов не первой свежести, и чтобы в любой момент была возможность быстро обработать этот огромный объем данных…
Тут то на сцену и выходит Hadoop – и говорит, дружище – складывай файлики любых размеров на любое количество накопителей, а я тебе если что помогу быстро эти данные обработать и вытащить – например, построить табличку по всем операциям Иванова за последние 5 лет.
Терминология
Самое время сделать небольшую ремарку: мне (и не только мне, полмиллиона просмотров и тысяча закладок) очень понравилась вот эта статья про Hadoop и его компоненты. Написана очень простым и понятным языком, по-доброму. Здесь я стремлюсь повторить у вас те же чувства, что я испытал сам после прочтения той статьи. Рекомендую ознакомится.
Раскроем значения некоторых терминов и аббривеатур:
HDFS
Яндекс GPT утверждает, что HDFS a.k.a. Hadoop Distributed File System — это распределённая файловая система, предназначенная для работы с большими данными в экосистеме Hadoop на распределённых кластерах.
Отечественная LLM поясняет, что HDFS разбивает файлы на блоки (по умолчанию размером 128 МБ) и хранит их на разных узлах кластера. Это обеспечивает параллельную обработку данных и высокую скорость доступа. А для обеспечения отказоустойчивости каждый блок дублируется на несколько узлов. Если один узел выходит из строя, данные восстанавливаются с других узлов.
В целом с нейросетью сложно не согласится, однако не совсем понятно, как хранение на разных узлах кластера обеспечивает высокую скорость доступа к данным. Этот момент рассмотрим подробнее.

Глядя на картинку из интернета, видно, что некая NameNode является обязательной частью обработки любого запроса с клиента. Это нода кластера, которая хранит информацию (метаданные) о том, на каких DataNode хранится файл. Поэтому планировщик (механизм, отвечающий за распараллеливание и запуск процессов, извлекающих данные) спрашивает у NameNode, по каким DataNode раскидан файл, и собирает его в параллели с нескольких нод.
Ну и соответственно общая скорость отдачи данных близится к скорости каждого диска * кол-во дисков. Это позволяет нам почти до бесконечности масштабировать вычислительные мощности кластера. В случае с одной огромной нодой такое, как вы понимаете, невозможно.
Map Reduce
Данные нам необходимо не только сохранять и извлекать, но и трансформировать. А для какой-либо обработки данных, которые раскиданы в распределенном хранилище, нам нужен план того, как мы будем это делать.
Итак, MapReduce — это программная модель и подход к обработке больших данных, придуманный для параллельной обработки очень больших файлов на кластере машин. MapReduce разбивает задачу на две простые фазы: Map (преобразование) и Reduce (агрегация).
Есть еще этап сортировки, и с ним процесс обработки данных можно описать следующим образом:
Map: берём входные данные (файлы), каждый файл обрабатываем локально и превращаем в набор пар ключ → значение (key → value).
Shuffle/Sort: группируем по ключам — все значения с одним ключом собираются вместе (это шаг, который выполняет обмен данными между узлами).
Reduce: для каждого ключа применяем функцию, которая агрегирует список значений в итог (например, суммирует).
В качестве примера можно привести избитый алгоритм подсчёта слов в тексте:
Map: Получаем строку текста и выдаём пары (слово, 1) для каждого слова в строке.
Shuffle: Собираем все кортежи вместе(группируем значения (наши единички) по ключу – самому слову).
Reduce: Суммируем значения по ключу (например, для "hello" получаем список [1,1,1,...] и превращаем его в (hello, 42) ).
На выходе получаем словарик ключ-значение, с тем, сколько раз каждое слово встречается в тексте. Прелесть в том, что первый этап (Map) может проходить параллельно для каждой строчки из текста (в реальности – на каждой ноде, где лежат данные), а также то, что сами данные никуда не переносятся, а обрабатываются локально (на первом этапе расчета, разумеется).
Также MapReduce называют одноименную реализацию данной модели вычислений в экосистеме Hadoop.
YARN
Про YARN скажу совсем кратенько: YARN (Yet Another Resource Negotiator) — это менеджер ресурсов Hadoop-кластера, который распределяет CPU и память в виде контейнеров между приложениями (Spark, MapReduce, Tez и др.). Он позволяет нескольким фреймворкам безопасно делить один кластер, обеспечивает учёт и изоляцию ресурсов, а также применяет политики планирования (очереди/квоты) для соблюдения SLA.
Внутри этого менеджера ресурсов есть несколько компонентов:
ResourceManager — мастер-компонент, отвечает за глобальное распределение ресурсов по приложениям и очередям. Ничего не знает про выполняемые на контейнерах задачи.
NodeManager — агент на каждом узле: запускает/мониторит контейнеры, отчитывается ResourceManager о состоянии узла.
ApplicationMaster — компонент, который договаривается с RM о контейнерах для конкретного приложения и управляет его задачами (MapReduce джобами, Spark-on-YARN приложениями и т.п.).

И очень упрощенно процесс обработки можно описать так:
Клиент подаёт заявку на запуск приложения (например Spark job).
ResourceManager запускает ApplicationMaster для этого приложения.
ApplicationMaster запрашивает у ResourceManager контейнеры (с указанием памяти/CPU/локальных предпочтений).
ResourceManager выделяет контейнеры на NodeManagers, а ApplicationMaster запускает задачи внутри этих контейнеров.
NodeManagers следят за здоровьем и отчитываются ResourceManager'у.
Hadoop
Настал момент, когда мы можем приблизительно описать, что такое хадуп:
Hadoop — это платформа для дешёвого масштабируемого хранения и параллельной обработки больших данных, где HDFS отвечает за надёжное распределённое хранение, YARN — за управление ресурсами, а обширная экосистема (MapReduce, Spark, Hive и пр.) обеспечивает ETL и интеграцию со сторонними системами.

Закрепим: чем же хорош Hadoop? Он позволяет не тягать огромные объемы данных куда-либо для обработки. Он хранит их на кластере дешевых машин, и умеет обрабатывать любые запросы к этим данным параллельно, запуская вычисления на каждой машине (и на каждом кусочке хранимых на ней данных) отдельно – и затем агрегировать и выдавать результат. Также Hadoop отлично реплицирует данные, обеспечивая отличную отказоустойчивость. Это позволяет горизонтально масштабировать ваш кластер очень и очень долго, получая при этом отличную производительность.
Spark
Apache Spark — фреймворк с открытым исходным кодом для распределённой обработки данных. Фреймворк входит в экосистему проектов Hadoop, хотя и является самостоятельным продуктом, и может использоваться и без Hadoop.
Spark – это штука, которая обрабатывает данные на распределенных кластерах. Пока по описанию очень похоже на Hadoop MapReduce, да?
По сути, этот фреймворк – более эффективная замена MapReduce. Основные отличия можно описать так:
MapReduce — это жёсткая модель: Map → Shuffle → Reduce, и промежуточные результаты между шагами обычно пишутся на диск.
Spark — универсальный движок, который строит произвольный граф выполняемых операций, старается по максимуму использовать оперативную память, а не диск, а также оптимизирует план выполнения сложных задач.

Из-за этого Spark почти во всех реальных задачах работает гораздо быстрее и эффективнее.
Кроме того, Spark адаптирован под несколько ЯП: Python (PySpark), Scala (родной язык Spark), Java, R (SparkR), а также Spark SQL.
Row-layout формат
Форматы хранения данных на диске и их вычитки определенно не являются главной вещью, которую я хотел освещать в статье, однако я подумал, что про row/column-based формат хранения данных обязательно необходимо упомянуть, потому что данные, хранящиеся в Hadoop кластере, не предназначены для OLTP формата взаимодействия. А в случае с аналитическими запросами колоночный формат хранения данных очень часто используется на практике (parquet, orc, СУБД ClickHouse, MPP Greenplum).

Итак, для начала рассмотрим привычный нам, строковый формат хранения данных (таблиц). При таком формате записи в таблице сохраняются на диске построчно. Например :
[1,Anna,100.0,2025-01-01]
[2,Boris,50.0,2025-01-02]
[3,Claire,200.0,2025-01-03]
Такие блоки хранятся на диске последовательно. Соответственно в сценариях, в которых нам нужно вытащить запись по ключу и прочитать много ее полей, либо обновить какие-то из них – эффективнее будет именно такой формат. Вытащили запись, и изменяем ее – параллельно другая транзакция может работать с другой записью. С точки зрения диска это будет гораздо быстрее, чем колоночный формат.
Column-layout формат
При колоночном подходе мы храним каждый столбец таблицы на диске отдельно. Например:
id: [1,2,3]
name: [Anna,Boris,Claire]
amount: [100.0,50.0,200.0]
ts: [2025-01-01, 2025-01-02, 2025-01-03]
Теперь уже каждая колонка сохраняется на диске последовательно. Здесь будут эффективно отрабатывать случаи, когда из большой таблицы с множеством колонок нам надо извлечь только значения только некоторых колонок, например: SELECT id, amount FROM table WHERE ts BETWEEN '2025-01-01' AND '2025-01-31' – в таком случае можно прочитать только колонки ts (для фильтра) и id, amount (для результата), а остальные колонки (если представить, что в нашей таблице их под сотню) не читать вообще.
Соответственно в большинстве случаев мы можем читать только те данные, которые у нас запросили – это круто и удобно. Также становится гораздо удобнее сжимать данные, когда они хранятся по колонкам: status[SUCCESS, SUCCESS, SUCCESS, SUCCESS, ERROR, ERROR…]
Однако везде есть свои минусы. В данном случае в минусы будут записаны почти все плюсы row-layout формата. Т.е.:
Такой формат не применим к кейсам, где необходимо обрабатывать транзакционные операции с низкой латентностью (которые должны завершиться быстро: например, списание денег со счета). В таком случае мы готовы пожертвовать лишней памятью и вычитать лишние данные. Нам гораздо важнее, что найденная строка будет содержать сразу все в одном месте. Нам не надо будет бегать по разным местам диска и собирать нужную строку по кусочкам (см. random reads).
Также column-layout формат не применим к ситуациям, где требуются быстрые небольшие записи/обновления – по тем же причинам: скажем нам надо вычитать ключ из одного места на диске, пойти в другое, найти там нужную колонку – и записать в нее обновленное значение.
Имеет свое место и исторический контекст: под OLTP исторически заточен именно row-layout формат: эффективные механизмы блокировок, MVCC, быстрые коммиты. При тысячах мелких транзакций в секунду row-store обычно даёт стабильную низкую латентность. Column-store же (насколько мне известно) больше оптимизирован под пакетные обработки (например, батчевые записи), и помогает уменьшить нагрузку на диск (что при работе с огромными объемами данных дает значительный прирост к скорости).
Выводы
Когда использовать Hadoop?
Когда данных ОЧЕНЬ много, и надо где-то хранить и обрабатывать (в OLAP режиме) ОЧЕНЬ большие объемы быстро. При этом объемы настолько большие, что обработка на одной машине, даже очень мощной, невозможна и требуются распределенные многопоточные вычисления.
Когда использовать реляционную СУБД?
Когда мы работаем в OLTP формате и нам нужен быстрый отклик для получения «горячих» данных. Когда нам нужна строгая ACID-консистентность и простая интеграция с существующими приложениями.
Да и вообще: если ты точно не знаешь, что тебе использовать – используй реляционную СУБД.
Когда появляется необходимость работать с реляционными представлениями поверх Hadoop?
Когда нужно обеспечить возможность запроса данных на Hadoop кластер в SQL-формате, и при этом такие запросы тяжелые и не частые (например, перестраивать витрину данных раз в сутки, либо руками выполнять аналитические запросы).
Кстати, есть даже СУБД, работающие поверх Hadoop (Hive, HBase, Cassandra и др.) – если есть желание, рекомендую ознакомится.
Важное предупреждение
Здесь, уже после того, как мы поняли, что такое Hadoop, я хотел бы сделать лирическое отступление: скорее всего, вам не нужен Hadoop для решения ваших задач. Вы не гугл.
“MapReduce и Hadoop в этом плане являются лёгкими мишенями для критики, ведь даже последователи карго-культа со временем признали, что их обряды как-то не очень работают для привлечения самолётов.
Но это наблюдение можно и обобщить: если вы решаете использовать какую-то технологию, созданную большой корпорацией, вполне может быть, что вы не пришли к этому решению сознательно; вполне возможно, что к этому привела какая-то мистическая вера в то, что можно имитировать поведение гигантов и таким образом достичь тех же высот.”
Вот здесь приведен яркий пример сказанного выше. Очень часто люди стремятся применить для решения своих задач классный инструмент, но не задумываются о том, какую проблему они действительно решают, и какие есть альтернативные пути ее решения. В статье, уже упомянутой выше, есть чек-лист, с которым стоит сверяться при подборе инструментария для решения любой крупной проблемы. Но это я так, к слову, чтобы вдруг впечатленный читатель не побежал решать все задачи на новом, прикольном стеке.
Для себя я сделал вывод, что мне, как разработчику, становится экспертом в Hadoop и уметь применять его на практике не обязательно. Однако понимать (хотя бы концептуально, опуская множество деталей реализации), как в целом работает система, которую ты сопровождаешь - важно и нужно, даже если твоя зона ответственности достаточно мала.
Спасибо за внимание!
Комментарии (2)
SergeiMinaev
21.08.2025 17:20Сам я являюсь разработчиком, и ежедневно взаимодействую с различными СУБД – в основном, с пресловутой PostgreSQL.
"Пресловутый - широко известный своими сомнительными, отрицательными качествами". Вроде с постгрес в этом плане всё ок.
dmitrye1
Принцип локальности данных не очень дружит с облаками, увы.