Как хранятся партиционированные таблицы в HDFS?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Партиционирование таблиц в HDFS
Что такое партиционирование
Партиционирование — это техника разделения больших таблиц на логические подмножества (партиции) на основе значений одного или нескольких столбцов. В контексте HDFS это означает создание отдельных директорий для каждого значения партиционирующего ключа.
Физическая структура партиционированной таблицы в HDFS
Непартиционированная таблица:
/user/hive/warehouse/transactions/
├── part-m-00000.parquet
├── part-m-00001.parquet
├── part-m-00002.parquet
└── part-m-00003.parquet
Партиционированная таблица по дате:
/user/hive/warehouse/transactions/
├── year=2023/
│ ├── month=01/
│ │ ├── day=01/
│ │ │ ├── part-m-00000.parquet
│ │ │ └── part-m-00001.parquet
│ │ └── day=02/
│ │ ├── part-m-00000.parquet
│ │ └── part-m-00001.parquet
│ └── month=02/
│ └── ...
└── year=2024/
└── ...
Уровни партиционирования
Пример 1: Простое партиционирование (1 уровень)
CREATE TABLE transactions (
transaction_id INT,
customer_id INT,
amount DECIMAL(10,2),
transaction_date DATE
)
PARTITIONED BY (
year INT,
month INT
)
STORED AS PARQUET;
Физическая структура в HDFS:
/warehouse/transactions/
├── year=2023/month=01/
├── year=2023/month=02/
├── year=2023/month=03/
└── year=2024/month=01/
Пример 2: Многоуровневое партиционирование (3 уровня)
CREATE TABLE events (
event_id INT,
user_id INT,
event_type STRING,
event_value FLOAT
)
PARTITIONED BY (
year INT,
month INT,
day INT
)
STORED AS PARQUET;
Физическая структура в HDFS:
/warehouse/events/
├── year=2023/month=01/day=01/
├── year=2023/month=01/day=02/
├── year=2023/month=01/day=03/
└── year=2023/month=01/day=04/
Как Hive/Spark управляет партициями
Добавление данных в партицию
# Spark DataFrame
from pyspark.sql import SparkSession
from datetime import datetime
spark = SparkSession.builder.appName('partitioning').getOrCreate()
# Создание DataFrame с данными
df = spark.read.parquet('/data/raw/transactions')
# Добавление колонок для партиционирования
from pyspark.sql.functions import year, month, day, col
df_with_partitions = df.select(
col('transaction_id'),
col('customer_id'),
col('amount'),
year(col('transaction_date')).alias('year'),
month(col('transaction_date')).alias('month'),
day(col('transaction_date')).alias('day')
)
# Написание с партиционированием
df_with_partitions.write \
.mode('append') \
.partitionBy('year', 'month', 'day') \
.parquet('/warehouse/transactions')
Результат в HDFS:
/warehouse/transactions/
├── year=2023/month=01/day=15/
│ ├── part-00000-abc123.parquet
│ └── part-00001-def456.parquet
├── year=2023/month=01/day=16/
│ ├── part-00000-ghi789.parquet
│ └── part-00001-jkl012.parquet
└── ...
Добавление существующих файлов в метаметаданные
-- Добавление одной партиции
ALTER TABLE transactions ADD PARTITION (year=2023, month=01)
LOCATION '/warehouse/transactions/year=2023/month=01';
-- Добавление нескольких партиций
ALTER TABLE transactions ADD PARTITION (year=2023, month=02)
PARTITION (year=2023, month=03)
PARTITION (year=2023, month=04);
-- Автоматическое сканирование и добавление всех партиций
MSCK REPAIR TABLE transactions;
Файлы на диске в каждой партиции
Структура одной партиции:
/warehouse/transactions/year=2023/month=01/day=15/
├── part-00000-uuid.parquet (chunk 1)
├── part-00001-uuid.parquet (chunk 2)
├── part-00002-uuid.parquet (chunk 3)
├── _SUCCESS
├── _temporary/
└── .spark_schema (метаданные схемы)
Объяснение:
part-*файлы: актуальные данные_SUCCESS: маркер успешного завершения_temporary: временные файлы записи.spark_schema: JSON со схемой таблицы
Пример физического хранения
# Просмотр HDFS структуры
hdfs dfs -ls /warehouse/transactions/
# Вывод:
# year=2023
# year=2024
hdfs dfs -ls /warehouse/transactions/year=2023/
# Вывод:
# month=01
# month=02
hdfs dfs -ls /warehouse/transactions/year=2023/month=01/
# Вывод:
# day=01
# day=02
hdfs dfs -ls /warehouse/transactions/year=2023/month=01/day=01/
# Вывод:
# -rw-r--r-- 3 hive hadoop 1073741824 2026-03-26 10:00 part-00000.parquet
# -rw-r--r-- 3 hive hadoop 1073741824 2026-03-26 10:00 part-00001.parquet
# -rw-r--r-- 3 hive hadoop 1024 2026-03-26 10:00 _SUCCESS
Производительность при чтении
Запрос БЕЗ фильтра по партиции:
SELECT COUNT(*) FROM transactions;
Spark должен сканировать ВСЕ файлы во ВСЕХ партициях:
Читает:
/warehouse/transactions/year=2023/month=01/day=*/part-*.parquet
/warehouse/transactions/year=2023/month=02/day=*/part-*.parquet
/warehouse/transactions/year=2023/month=03/day=*/part-*.parquet
...
/warehouse/transactions/year=2024/month=12/day=*/part-*.parquet
Запрос С фильтром по партиции (PARTITION PRUNING):
SELECT COUNT(*) FROM transactions
WHERE year=2023 AND month=01 AND day=15;
Spark сканирует ТОЛЬКО нужные файлы:
Читает только:
/warehouse/transactions/year=2023/month=01/day=15/part-*.parquet
Экономия:
- Без фильтра: 3 года × 12 месяцев × 31 дней = ~1116 партиций
- С фильтром: 1 партиция
- Ускорение: ~1000x
Метаданные партиций
МетаStore хранит информацию о всех партициях:
-- Просмотр всех партиций
SHOW PARTITIONS transactions;
-- Вывод:
# year=2023/month=01
# year=2023/month=02
# year=2023/month=03
# year=2024/month=01
-- Просмотр деталей конкретной партиции
DESC FORMATTED transactions PARTITION (year=2023, month=01);
-- Получение количества файлов в партиции
SELECT COUNT(*) as file_count
FROM transactions
WHERE year=2023 AND month=01;
Распределение данных в партициях
Обычно в каждую партицию попадают несколько блоков HDFS:
Партиция: year=2023/month=01/day=01
├── part-00000.parquet (128 MB, блоки: 0-1)
├── part-00001.parquet (128 MB, блоки: 2-3)
└── part-00002.parquet (50 MB, блоки: 4-5)
Так как HDFS блок = 128 MB (по умолчанию)
То 3 файла размером (128+128+50)=306 MB будут размещены на:
- DataNode 1: block 0
- DataNode 2: block 1
- DataNode 3: block 2
- DataNode 1: block 3 (репликация)
- ...
Оптимальный размер партиции
# Рекомендация для размера одной партиции
Optimal_partition_size = HDFS_block_size × (number_of_datanode_replicas)
# Обычно:
# - Если файл < 1 GB: слишком маленькая партиция (много файлов открывается)
# - Если файл > 100 GB: слишком большая партиция (медленнее читать)
# - Оптимально: 1-10 GB на партицию
# Пример оптимизации
df.write \
.partitionBy('year', 'month', 'day') \
.option('maxPartitionBytes', '10gb') \
.parquet('/warehouse/transactions')
Динамическое партиционирование (Dynamic Partitioning)
# Spark динамически создаёт партиции автоматически
df.write \
.mode('append') \
.partitionBy('year', 'month', 'day') \
.parquet('/warehouse/transactions')
# Spark анализирует значения в столбцах
# и создаёт директории year=*/month=*/day=*/ автоматически
Лучшие практики партиционирования
# 1. Не партиционировать по слишком высокой кардинальности
# Плохо: PARTITIONED BY (user_id) -- миллионы разных значений!
# Хорошо: PARTITIONED BY (region, year, month)
# 2. Использовать иерархические ключи
# Хорошо: PARTITIONED BY (year, month, day) -- иерархическая структура
# Плохо: PARTITIONED BY (timestamp) -- хаотичные директории
# 3. Выравнивать размер партиций
df.write \
.partitionBy('region', 'year', 'month') \
.option('spark.sql.shuffle.partitions', 200) \
.parquet('/warehouse/data')
# 4. Использовать Parquet/ORC с партиционированием
# Хорошо для:
# - Быстрого чтения (partition pruning)
# - Параллельного процесса (каждая партиция = разные executor'ы)
# - Управления удалением устаревших данных
Примеры использования
Пример 1: Покупки по регионам и датам
CREATE TABLE purchases (
purchase_id INT,
customer_id INT,
amount DECIMAL(10,2),
product_name STRING
)
PARTITIONED BY (
region STRING,
year INT,
month INT
)
STORED AS PARQUET;
-- HDFS структура:
-- /warehouse/purchases/region=USA/year=2023/month=01/
-- /warehouse/purchases/region=USA/year=2023/month=02/
-- /warehouse/purchases/region=EU/year=2023/month=01/
-- /warehouse/purchases/region=ASIA/year=2024/month=01/
Пример 2: Логи приложения
CREATE TABLE app_logs (
log_id INT,
user_id INT,
event_type STRING,
message STRING
)
PARTITIONED BY (
log_date STRING, -- YYYY-MM-DD
hour INT -- 0-23
)
STORED AS PARQUET;
-- Быстрое удаление старых логов
ALTER TABLE app_logs DROP PARTITION (log_date < '2026-01-01');
Проблемы и решения
| Проблема | Причина | Решение |
|---|---|---|
| Слишком много файлов в партиции | Мало executor'ов | Увеличить spark.sql.shuffle.partitions |
| Слишком много партиций | Высокая кардинальность | Использовать меньше уровней партиционирования |
| Медленное чтение | Неправильное partition pruning | Проверить WHERE условия |
| Несинхронизированные метаданные | Прямые операции в HDFS | Запустить MSCK REPAIR TABLE |
Заключение
Партиционирование в HDFS — это критическая техника для оптимизации больших данных:
- Физически: данные хранятся в иерархических директориях
- Логически: Spark использует partition pruning для быстрого чтения
- Метаданные: Hive MetaStore отслеживает все партиции
- Производительность: Правильное партиционирование дает 100x+ ускорение
Оптимальная стратегия партиционирования зависит от pattern доступа к данным и требует тестирования и мониторинга.