← Назад к вопросам

Как хранятся партиционированные таблицы в HDFS?

2.0 Middle🔥 101 комментариев
#Hadoop и распределенные системы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Партиционирование таблиц в 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 — это критическая техника для оптимизации больших данных:

  1. Физически: данные хранятся в иерархических директориях
  2. Логически: Spark использует partition pruning для быстрого чтения
  3. Метаданные: Hive MetaStore отслеживает все партиции
  4. Производительность: Правильное партиционирование дает 100x+ ускорение

Оптимальная стратегия партиционирования зависит от pattern доступа к данным и требует тестирования и мониторинга.