Четвёртая статья цикла о построении CDC-пайплайна с нуля. Данные уже текут из PostgreSQL в Kafka — пора их куда-то складывать. Сегодня поднимаем Hadoop и Hive, и разбираемся, почему Hive 3.1.3 не дружит с Java 11.
Зачем я это пишу
Честно: в первую очередь это мои заметки. На работе я поддерживаю CDC-пайплайны — 7 баз данных, сотни миллионов записей. Данные появляются в хранилище, моя задача — чтобы они продолжали появляться. Но как именно они туда попадают — хочу разобраться, поэтому строю лабораторию дома.
Во вторую очередь — портфолио. Если буду менять работу, эти статьи покажут, что я понимаю весь путь данных, а не только свой кусочек.
И в третью — мотивация не бросить. Мы уже прошли большую часть пути, осталось немного.
Что уже есть
В предыдущих статьях я поднял:
Budget Parser — Telegram-бот, который парсит скриншоты банковских операций через Claude Vision API
PostgreSQL с logical replication — wal_level=logical, публикация, пользователь для репликации
Kafka + Debezium — CDC-события текут в топик budget.finance.parsed_transactions
Помните транзакции YM*DEEPHOST -313.95₽ и Красное&Белое -440.83₽? Они уже в Kafka. Теперь нужно место, куда их складывать для аналитики.
Целевая архитектура
СХЕМА
Полная схема: от скриншота до SQL-запросов. Сегодня — зеленая зона справа.

Сегодня поднимем HDFS и Hive, создадим структуру директорий и таблицу. Consumer напишем в следующей статье.
Почему VM, а не контейнер?
Все предыдущие компоненты я запускал в LXC-контейнерах. Для Hadoop решил использовать полноценную виртуальную машину:
HDFS требует специфичных системных вызовов
Hive лучше работает с полной виртуализацией
Меньше проблем с Java и memory management
Проще отлаживать — полноценная ОС
VM: ID 400, hostname hadoop, IP 192.168.0.229
Ресурсы: 8GB RAM, 4 cores, 50GB disk
ОС: Ubuntu 22.04 LTS
Часть 1: Подготовка системы
sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget vim htop net-tools
Java — здесь первые грабли
Изначально я установил OpenJDK 11. Спойлер: это была ошибка, которая проявится позже при запуске Hive.
# Сразу ставим Java 8 — Hive 3.1.3 не работает с Java 11
sudo apt install -y openjdk-8-jdk
Часть 2: PostgreSQL для Hive Metastore
Hive хранит метаданные (какие таблицы существуют, их схемы, расположение данных) в реляционной базе. По умолчанию используется Derby, но для чего-то серьёзнее тестов нужен PostgreSQL.
sudo apt install -y postgresql postgresql-contrib sudo -u postgres psql CREATE USER hive WITH PASSWORD 'hive_metastore_pwd'; CREATE DATABASE hive_metastore OWNER hive; GRANT ALL PRIVILEGES ON DATABASE hive_metastore TO hive; \q
В /etc/postgresql/14/main/pg_hba.conf добавляем перед строкой local all all peer:
local all hive md5 host all hive 127.0.0.1/32 md5
sudo systemctl restart postgresql psql -h 127.0.0.1 -U hive -d hive_metastore -c "SELECT 1;"
Часть 3: Установка Hadoop
Пол��зователь и дистрибутив
sudo useradd -m -s /bin/bash hadoop sudo passwd hadoop cd /opt sudo wget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz sudo tar -xzf hadoop-3.3.6.tar.gz sudo mv hadoop-3.3.6 hadoop sudo chown -R hadoop:hadoop /opt/hadoop sudo rm hadoop-3.3.6.tar.gz
Переменные окружения
Переключаемся на пользователя hadoop и редактируем ~/.bashrc:
sudo su - hadoop nano ~/.bashrc
Важно для SSH — без этого скрипты Hadoop не работают
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Java export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 # Hadoop export HADOOP_HOME=/opt/hadoop export HADOOP_INSTALL=$HADOOP_HOME export HADOOP_MAPRED_HOME=$HADOOP_HOME export HADOOP_COMMON_HOME=$HADOOP_HOME export HADOOP_HDFS_HOME=$HADOOP_HOME export HADOOP_YARN_HOME=$HADOOP_HOME export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export PATH=$PATH:$HADOOP_HOME/sbin:$HADOOP_HOME/bin export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib/native" source ~/.bashrc hadoop version # Hadoop 3.3.6
Часть 4: Конфигурация HDFS
Что такое HDFS — коротко
HDFS состоит из двух типов узлов:
NameNode — хранит метаданные: какие файлы существуют, на какие блоки разбиты, где лежит каждый блок. Сам данные не хранит.
DataNode — хранит реальные блоки данных (по умолчанию 128MB каждый).

core-site.xml
nano /opt/hadoop/etc/hadoop/core-site.xml
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop/tmp</value> </property> </configuration>
fs.defaultFS — точка входа в файловую систему. Когда пишешь hdfs dfs -ls /, клиент идёт на localhost:9000.
hadoop.tmp.dir — по умолчанию /tmp/hadoop-${user.name}, который пропадёт после ребута.
hdfs-site.xml
nano /opt/hadoop/etc/hadoop/hdfs-site.xml
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:///opt/hadoop/hdfs/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///opt/hadoop/hdfs/datanode</value> </property> </configuration>
dfs.replication=1 — фактор репликации. В production обычно 3 (каждый блок на трёх DataNode). У нас один узел.
hadoop-env.sh
nano /opt/hadoop/etc/hadoop/hadoop-env.sh
После комментариев добавляем:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
Дублируем JAVA_HOME, потому что демоны Hadoop запускаются в своём окружении, где .bashrc может не загрузиться.
Директории и SSH
Директории и SSH
mkdir -p /opt/hadoop/tmp mkdir -p /opt/hadoop/hdfs/namenode mkdir -p /opt/hadoop/hdfs/datanode # SSH для localhost — Hadoop использует его для запуска демонов ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost echo "SSH OK"
Грабля #1: PATH и SSH
После настройки SSH я запустил HDFS:
start-dfs.sh
И получил:
localhost: /usr/bin/env: 'bash': No such file or directory
Что произошло? Скрипты Hadoop используют shebang #!/usr/bin/env bash. Команда env ищет bash через PATH. Но при SSH-подключении PATH оказывается пустым!
Почему? Когда SSH запускает команду неинтерактивно, .bashrc не загружается:

Решение: Заменить shebang в скриптах Hadoop. Открываем каждый скрипт через nano и меняем первую строку:
Было:
#!/usr/bin/env bash
Стало:
#!/bin/bash
Файлы, которые нужно отредактировать:
/opt/hadoop/sbin/start-dfs.sh, stop-dfs.sh и другие .sh в этой директории
/opt/hadoop/libexec/*.sh
/opt/hadoop/bin/hdfs, yarn, mapred, hadoop
Также в /opt/hadoop/bin/hdfs после #!/bin/bash добавляем:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH
Часть 5: Запуск HDFS
Форматирование NameNode
hdfs namenode -format
Создаётся fsimage — начальный снимок пустой файловой системы. Делается один раз. Повторное форматирование = потеря всех данных.
Запуск и проверка
start-dfs.sh jps
jps
67557 Jps
34932 RunJar
34470 DataNode
34294 NameNode
34812 RunJar
34671 SecondaryNameNode

Все демоны работают: HDFS (NameNode, DataNode, SecondaryNameNode) и Hive (два RunJar)
Структура директорий для CDC
Структура директорий для CDC
hdfs dfs -mkdir -p /data/cdc/budget/landing/finance/parsed_transactions hdfs dfs -mkdir -p /data/cdc/budget/bronze/finance/parsed_transactions hdfs dfs -mkdir -p /data/cdc/budget/silver/finance/parsed_transactions hdfs dfs -mkdir -p /data/cdc/budget/validation/finance/parsed_transactions hdfs dfs -ls -R /data drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:47 /data/cdc/budget drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/bronze drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/bronze/finance drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/bronze/finance/parsed_transactions drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/landing drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/landing/finance drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/landing/finance/parsed_transactions drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/silver drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/silver/finance drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:46 /data/cdc/budget/silver/finance/parsed_transactions drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:47 /data/cdc/budget/validation drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:47 /data/cdc/budget/validation/finance drwxr-xr-x - hadoop supergroup 0 2026-01-12 11:47 /data/cdc/budget/validation/finance/parsed_transactions
Слой | Формат | Назначение |
landing | Avro | Сырые данные из Kafka |
bronze | Parquet | Преобразованные, типизированные |
silver | Parquet | Финальные, видны в Hive |
validation | — | Проверка качества данных |
Часть 6: Установка Hive
cd /opt sudo wget https://archive.apache.org/dist/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz sudo tar -xzf apache-hive-3.1.3-bin.tar.gz sudo mv apache-hive-3.1.3-bin hive sudo chown -R hadoop:hadoop /opt/hive sudo rm apache-hive-3.1.3-bin.tar.gz
Добавляем в ~/.bashrc:
# Hive export HIVE_HOME=/opt/hive export PATH=$PATH:$HIVE_HOME/bin source ~/.bashrc hive --version # Hive 3.1.3
Грабля #2: Hive и Java 11
Если бы я установил Java 11, при запуске HiveServer2 получил бы:
java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader
Причина: В Java 9+ изменили системный ClassLoader. Hive 3.1.3 использует старый API.
Варианты:
Флаги JVM --add-opens — не помогают
Hive 4.x — большие изменения, сложнее
Java 8 — работает с��азу
Поэтому в начале статьи мы сразу поставили Java 8.
Часть 7: Конфигурация Hive
Директории в HDFS
hdfs dfs -mkdir -p /user/hive/warehouse hdfs dfs -mkdir -p /tmp/hive hdfs dfs -chmod 775 /user/hive/warehouse hdfs dfs -chmod 777 /tmp/hive
JDBC-драйвер
cd /opt/hive/lib sudo wget https://jdbc.postgresql.org/download/postgresql-42.6.0.jar sudo chown hadoop:hadoop postgresql-42.6.0.jar
hive-site.xml
nano /opt/hive/conf/hive-site.xml
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <!-- PostgreSQL Metastore --> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:postgresql://localhost:5432/hive_metastore</value> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>org.postgresql.Driver</value> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>hive</value> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>hive_metastore_pwd</value> </property> <!-- Warehouse --> <property> <name>hive.metastore.warehouse.dir</name> <value>/user/hive/warehouse</value> </property> <!-- Metastore --> <property> <name>hive.metastore.uris</name> <value>thrift://localhost:9083</value> </property> <!-- HiveServer2 --> <property> <name>hive.server2.enable.doAs</name> <value>false</value> </property> <property> <name>hive.server2.thrift.port</name> <value>10000</value> </property> <!-- Отключаем ACID — не нужен для external tables --> <property> <name>hive.support.concurrency</name> <value>false</value> </property> <property> <name>hive.txn.manager</name> <value>org.apache.hadoop.hive.ql.lockmgr.DummyTxnManager</value> </property> </configuration>
Инициализация схемы
schematool -dbType postgres -initSchema
# schemaTool completed
Часть 8: Запуск Hive
Архитектура Hive

Metastore — отвечает на вопросы «какие таблицы есть?», «какие колонки?», «где данные?»
HiveServer2 — принимает SQL-запросы, строит план, читает данные из HDFS.
Запуск сервисов
hive --service metastore & sleep 20 hive --service hiveserver2 & sleep 40 jps
Вывод
29636 RunJar # Metastore
29755 RunJar # HiveServer2
29157 DataNode
28984 NameNode
29356 SecondaryNameNode
Проверка через beeline
beeline -u "jdbc:hive2://localhost:10000" -n hadoop -e "SHOW DATABASES;"

Hive работает, база cdc_budget создана
Часть 9: Создание таблицы для CDC
beeline -u "jdbc:hive2://localhost:10000" -n hadoop CREATE DATABASE cdc_budget; USE cdc_budget; CREATE EXTERNAL TABLE parsed_transactions ( id BIGINT, transaction_date DATE, merchant STRING, amount DECIMAL(12,2), transaction_type STRING, category STRING, is_verified BOOLEAN, cdc_operation STRING, cdc_timestamp TIMESTAMP ) STORED AS PARQUET LOCATION '/data/cdc/budget/silver/finance/parsed_transactions'; DESCRIBE FORMATTED parsed_transactions;

External table: Location указывает на silver-слой в HDFS
External vs Managed:
Managed — Hive управляет данными. DROP TABLE удалит и данные.
External — данные лежат где укажем, Hive только читает. DROP TABLE удалит только метаданные.
Для CDC используем external — данные будет писать consumer, Hive только читает.
Грабли, на которые можно наступить
1. PATH пустой при SSH
Симптом: /usr/bin/env: 'bash': No such file or directory
Причина: Скрипты Hadoop используют #!/usr/bin/env bash, но при неинтерактивном SSH .bashrc не загружается.
Решение: Заменить shebang на #!/bin/bash и добавить PATH в начало скриптов.
2. Hive не работает с Java 11
Симптом: ClassCastException: AppClassLoader cannot be cast to URLClassLoader
Причина: В Java 9+ изменили ClassLoader. Hive 3.1.3 использует старый API.
Решение: Использовать Java 8.
3. Повторное форматирование NameNode
Симптом: DataNode не подключается после hdfs namenode -format
Причина: Новый clusterID не совпадает с тем, что записан в DataNode.
Решение: Форматировать только один раз. Или удалить данные DataNode перед повторным форматированием.
4. Derby вместо PostgreSQL
Симптом: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient
Причина: Metastore не запущен или использует встроенный Derby (который блокирует базу).
Решение: Настроить PostgreSQL, запустить Metastore отдельным сервисом.
Итоговая архитектура
Компонент | Хост | Порт | Назначение |
Budget Parser | CT 301, 192.168.0.150 | — | Telegram-бот |
PostgreSQL (source) | CT 302, 192.168.0.151 | 5432 | Источник данных |
Kafka | CT 303, 192.168.0.153 | 9092 | Брокер сообщений |
Debezium | CT 304, 192.168.0.154 | 8083 | CDC-коннектор |
Kafka UI | CT 306, 192.168.0.160 | 8080 | Мониторинг Kafka |
HDFS NameNode | VM 400, 192.168.0.229 | 9000, 9870 | Метаданные HDFS |
Hive Metastore | VM 400, 192.168.0.229 | 9083 | Каталог таблиц |
HiveServer2 | VM 400, 192.168.0.229 | 10000 | SQL-интерфейс |
Полезные команды
Полезные команды
# Статус HDFS hdfs dfsadmin -report # Структура директорий hdfs dfs -ls -R /data # Java-процессы jps # Запуск/остановка HDFS start-dfs.sh stop-dfs.sh # Запуск Hive hive --service metastore & hive --service hiveserver2 & # SQL-клиент beeline -u "jdbc:hive2://localhost:10000" -n hadoop # Проверка портов ss -tlnp | grep -E '9000|9083|10000'
Что дальше
HDFS и Hive готовы принимать данные. Таблица cdc_budget.parsed_transactions создана и указывает на silver-слой.
Следующий этап — написать Consumer, который будет:
Читать CDC-события из Kafka
Преобразовывать JSON → Avro → Parquet
Раскладывать по слоям: landing → bronze → silver
Обрабатывать разные типы операций (INSERT/UPDATE/DELETE)
# | Статья | Статус |
1 | ✅ | |
2 | ✅ | |
3 | ✅ | |
4 | HDFS + Hive | ✅ Эта статья |
5 | Consumer: Kafka → HDFS | ⏳ Следующая |
6 | Мониторинг в Grafana | 📋 Планируется |
Ссылки
Вопросы — в комментарии. Кто ещё поднимает Hadoop дома? Какие грабли встречались? Интересно сравнить опыт.
