Колоночное хранение

Материал из llmdocs — справочник разработчика
Портал: Базы данных
Колоночное хранение
Сутьзначения одного столбца хранятся подряд
Противоположностьстроковое хранение (row store, OLTP)
Сильные сторонысжатие, чтение нужных столбцов, векторизация
Слабые стороныточечные обновления и вставки по одной строке
СУБДClickHouse, DuckDB, Vertica, Apache Druid, Snowflake, BigQuery, Redshift
ФорматыApache Parquet, ORC (на диске), Apache Arrow (в памяти)

Колоночное хранение (англ. columnar storage, column-oriented) — способ физической организации таблицы, при котором значения каждого столбца хранятся на диске и в памяти подряд, отдельно от других столбцов. Классические реляционные СУБД вроде PostgreSQL и MySQL хранят данные по строкам: все поля одной записи лежат рядом. Колоночная организация делает аналитические запросы по огромным таблицам в десятки и сотни раз быстрее и лежит в основе ClickHouse, облачных хранилищ данных и форматов Parquet и Arrow.[1]

Строки или столбцы

Пусть таблица events содержит миллиард строк и 50 столбцов, а запрос считает выручку по дням:

SELECT toDate(ts) AS day, sum(amount)
FROM events
WHERE country = 'RU'
GROUP BY day;

Запросу нужны только три столбца — ts, amount, country. Строковая СУБД вынуждена прочитать с диска все 50 столбцов каждой строки; колоночная читает только три файла, то есть примерно в 15 раз меньше данных ещё до сжатия.

Строковое (OLTP) Колоночное (OLAP)
Типичный запрос «заказ № 1042 целиком» «сумма по миллиардам строк»
Чтение одной записи быстро медленно (сборка из многих столбцов)
Вставка, обновление по одной строке быстро дорого, лучше пачками
Агрегаты по немногим столбцам медленно очень быстро
Сжатие умеренное сильное, часто в 5–20 раз
Примеры PostgreSQL, MySQL, SQLite ClickHouse, DuckDB, BigQuery

Поэтому системы обычно делят по нагрузке: транзакционная база (OLTP) обслуживает приложение, а данные из неё копируются в аналитическое колоночное хранилище (OLAP).

Почему это быстро

1. Сжатие. Значения одного столбца однотипны и часто похожи, поэтому они хорошо сжимаются специализированными кодировками:[3]

  • словарная — строки с небольшим числом вариантов (страна, статус) заменяются короткими номерами;
  • RLE (run-length) — повторы «RU, RU, RU…» хранятся как «RU × 1000»;
  • дельта — для отсортированных времён и счётчиков хранятся разности соседних значений;
  • поверх — универсальные алгоритмы LZ4 и Zstandard.

Сжатие уменьшает не только диск, но и объём чтения — а именно ввод-вывод обычно ограничивает аналитику.

2. Векторизованное выполнение. Движок обрабатывает данные не по одной строке, а блоками по тысячам значений одного столбца. Это хорошо ложится на кэши процессора и SIMD-инструкции; подход предложен в исследовательской СУБД MonetDB/X100 (2005).[7]

3. Пропуск данных. Для блоков хранятся минимумы и максимумы, разреженные индексы и фильтры Блума, так что блоки, заведомо не подходящие под условие WHERE, не читаются вовсе.

4. Параллелизм. Столбцы и блоки обрабатываются независимо на всех ядрах и узлах кластера.

История

Идея известна с 1970-х, но массовой стала после исследовательских систем MonetDB и C-Store (Майкл Стоунбрейкер и соавторы, 2005),[2] из которой выросла коммерческая Vertica. В 2010-х появились Google Dremel (BigQuery), Amazon Redshift, Apache Druid, Snowflake и открытый ClickHouse от Яндекса (2016). В 2019 году вышла DuckDB — встраиваемая колоночная СУБД, «SQLite для аналитики».[6]

Форматы файлов

Колоночная организация применяется и вне СУБД:

  • Apache Parquet — стандартный колоночный формат для озёр данных: файл делится на группы строк, внутри — колонки со сжатием и статистикой.[4] Его читают Spark, pandas, Polars, DuckDB, ClickHouse, Trino.
  • ORC — похожий формат из экосистемы Hadoop/Hive.
  • Apache Arrow — колоночный формат в памяти для обмена данными между процессами и языками без копирования и сериализации.[5] На нём построены pandas 2.x (опционально), Polars и многие драйверы.
import duckdb
duckdb.sql("""
    SELECT country, count(*) AS n, avg(amount)
    FROM 'events/*.parquet'
    GROUP BY country ORDER BY n DESC
""").show()

Табличные форматы Apache Iceberg и Delta Lake добавляют поверх Parquet-файлов транзакции, версии и эволюцию схемы.

Ограничения

  • Точечные обновления и удаления дороги: ClickHouse выполняет их как фоновые «мутации», другие системы — через пометки об удалении и последующее слияние.
  • Вставлять данные нужно пачками (тысячи строк и больше), иначе плодятся мелкие части.
  • Запросы «одна строка целиком» и множество соединений по ключам эффективнее в строковых СУБД.
  • Транзакционные гарантии обычно слабее, чем в OLTP-базах.

Существуют и гибриды (HTAP): колоночные индексы в SQL Server, расширения для PostgreSQL вроде Citus columnar и pg_duckdb, отдельный колоночный движок MariaDB ColumnStore.

См. также

Источники

  1. Abadi, Boncz, Harizopoulos et al. — The Design and Implementation of Modern Column-Oriented Database Systems (2013)
  2. Stonebraker et al. — C-Store: A Column-oriented DBMS (VLDB 2005)
  3. ClickHouse — Why is ClickHouse so fast?
  4. Apache Parquet documentation
  5. Apache Arrow — Columnar Format
  6. DuckDB documentation
  7. Boncz, Zukowski, Nes — MonetDB/X100: Hyper-Pipelining Query Execution (CIDR 2005)