Четвёртая статья цикла о построении CDC-пайплайна с нуля. Данные уже текут из PostgreSQL в Kafka — пора их куда-то складывать. Сегодня поднимаем Hadoop и Hive, и разбираемся, почему Hive 3.1.3 не дружит с Java 11.


Зачем я это пишу

Честно: в первую очередь это мои заметки. На работе я поддерживаю CDC-пайплайны — 7 баз данных, сотни миллионов записей. Данные появляются в хранилище, моя задача — чтобы они продолжали появляться. Но как именно они туда попадают — хочу разобраться, поэтому строю лабораторию дома.

Во вторую очередь — портфолио. Если буду менять работу, эти статьи покажут, что я понимаю весь путь данных, а не только свой кусочек.

И в третью — мотивация не бросить. Мы уже прошли большую часть пути, осталось немного.


Что уже есть

В предыдущих статьях я поднял:

  1. Budget Parser — Telegram-бот, который парсит скриншоты банковских операций через Claude Vision API

  2. PostgreSQL с logical replication — wal_level=logical, публикация, пользователь для репликации

  3. 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.

Варианты:

  1. Флаги JVM --add-opens — не помогают

  2. Hive 4.x — большие изменения, сложнее

  3. 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, который будет:

  1. Читать CDC-события из Kafka

  2. Преобразовывать JSON → Avro → Parquet

  3. Раскладывать по слоям: landing → bronze → silver

  4. Обрабатывать разные типы операций (INSERT/UPDATE/DELETE)

#

Статья

Статус

1

Budget Parser: Telegram + Claude Vision

2

PostgreSQL для CDC

3

Kafka + Debezium

4

HDFS + Hive

✅ Эта статья

5

Consumer: Kafka → HDFS

⏳ Следующая

6

Мониторинг в Grafana

📋 Планируется


Ссылки


Вопросы — в комментарии. Кто ещё поднимает Hadoop дома? Какие грабли встречались? Интересно сравнить опыт.