Перейти к содержимому

Колоночные

Колоночные базы данных — физическое хранение данных по столбцам, а не строкам. Преимущества для OLAP: чтение только нужных столбцов, лучшее сжатие (dictionary, delta, RLE), векторизованная обработка (SIMD), пропуск блоков по метаданным. Популярные системы: ClickHouse (аналитика, логи, временные ряды), Cassandra (высокая доступность, масштабируемость, AP), HBase (Hadoop), Druid (временные ряды). Модели данных (partition key + clustering columns в Cassandra, ORDER BY в ClickHouse), сравнение с row-oriented хранением, критерии выбора и типичные ошибки.

Введение: Другой взгляд на хранение данных

Представьте огромную таблицу в Excel на миллион строк. Вам нужно вычислить среднюю зарплату всех сотрудников. В обычном (строковом) хранении компьютер читает всю таблицу целиком — каждую строку, каждую ячейку, включая имена, адреса, даты рождения. Даже если вам нужен только один столбец “зарплата”, компьютер вынужден читать всю таблицу.

А что если хранить таблицу не по строкам, а по столбцам? Все значения зарплат лежат вместе в одном месте. Чтобы вычислить среднюю зарплату, компьютер читает только этот блок данных. Ничего лишнего. Это значительно быстрее.

Колоночные базы данных (Column-Oriented Databases) — это базы данных, которые физически хранят данные по столбцам, а не по строкам. Вместо того чтобы записывать строку за строкой, они записывают столбец за столбцом.

Это фундаментальное отличие от реляционных баз, которые используют строковое хранение (row-oriented). Колоночное хранение дает колоссальный выигрыш в производительности для аналитических запросов (OLAP) — тех, которые читают много строк, но только несколько столбцов. Именно поэтому колоночные базы стали стандартом для хранилищ данных, аналитики, бизнес-интеллекта и больших данных.

Строковое vs Колоночное хранение

Строковое хранение (Row-Oriented)

Традиционный подход реляционных баз (PostgreSQL, MySQL, Oracle). Данные на диске хранятся последовательно: сначала все поля первой строки, потом все поля второй строки, и так далее.

Таблица “Сотрудники”:

idnameagecitysalary
1Иван30Москва50000
2Петр25СПб45000
3Анна35Казань55000

Строковое хранение на диске:

[1,Иван,30,Москва,50000] [2,Петр,25,СПб,45000] [3,Анна,35,Казань,55000]

Когда строковое хранение хорошо:

  • Операции с одной строкой (INSERT, UPDATE, DELETE)
  • Запросы, которые читают все столбцы строки (SELECT *)
  • OLTP-системы (онлайн-транзакции)

Когда строковое хранение плохо:

  • Аналитические запросы (читают много строк, но мало столбцов)
  • Агрегации (SUM, AVG, COUNT)
  • Сканирование больших таблиц

Колоночное хранение (Column-Oriented)

Данные хранятся последовательно по столбцам: сначала все значения первого столбца, потом все значения второго, и так далее.

Колоночное хранение на диске:

id:      [1, 2, 3]
name:    [Иван, Петр, Анна]
age:     [30, 25, 35]
city:    [Москва, СПб, Казань]
salary:  [50000, 45000, 55000]

Когда колоночное хранение хорошо:

  • Аналитические запросы (много строк, мало столбцов)
  • Агрегации (только нужные столбцы)
  • Сжатие (значения одного типа в одном блоке)

Когда колоночное хранение плохо:

  • Операции с одной строкой (дорого собрать строку из колонок)
  • Частые UPDATE и DELETE
  • OLTP-системы (онлайн-транзакции)

Почему колоночное хранение быстрее для аналитики

Причина 1: Чтение только нужных столбцов

Аналитический запрос:

SELECT AVG(salary) FROM employees WHERE city = 'Москва';
  • Строковое хранение: Нужно прочитать все столбцы всех строк, чтобы добраться до salary и city. Даже если в таблице 100 столбцов, а нужны только 2.
  • Колоночное хранение: Читаются только блоки данных для столбцов city и salary. Остальные столбцы не трогаются.

Выигрыш: В 10-100 раз, в зависимости от количества столбцов в таблице.

Причина 2: Лучшее сжатие

В колоночном хранении все значения одного столбца лежат вместе и имеют один тип данных. Это позволяет использовать эффективные алгоритмы сжатия.

Тип столбцаАлгоритм сжатияКоэффициент сжатия
Мало уникальных значений (city, status)Dictionary encoding (словарь)10:1 - 1000:1
Числа с небольшим диапазоном (age, year)Delta encoding (разности)5:1 - 20:1
Монотонные последовательности (timestamp, id)Run-length encoding (RLE)100:1 - 10000:1
Текстовые столбцыLZ4, ZSTD3:1 - 10:1

Пример: столбец city

Исходные данные: Москва, СПб, Москва, СПб, Москва, СПб, Москва, СПб... (1 млн строк)
Словарь: {1: "Москва", 2: "СПб"}
Сжатые данные: [1,2,1,2,1,2,1,2...]
Итоговое сжатие: ~1000:1

Причина 3: Векторизованная обработка (SIMD)

Современные процессоры могут обрабатывать несколько значений за одну инструкцию (SIMD — Single Instruction, Multiple Data). Колоночное хранение идеально подходит для SIMD, потому что значения одного типа лежат в непрерывном блоке памяти.

// Векторизованная обработка (псевдокод)
int sum = 0;
int* salaries = column_data;  // Непрерывный блок из 1000 зарплат
for (int i = 0; i < 1000; i += 8) {
    // SIMD инструкция складывает 8 чисел за раз
    sum += vector_sum(salaries + i);
}

Причина 4: Пропуск ненужных блоков (Zone Maps / MinMax Index)

Колоночные базы хранят для каждого блока данных метаинформацию: минимум, максимум, количество уникальных значений.

SELECT AVG(salary) FROM employees WHERE age > 50;

Если блок данных имеет метаинформацию age: min=25, max=40, то этот блок вообще не читается. Он не может содержать строки с age > 50.

Популярные колоночные базы данных

ClickHouse

ClickHouse — колоночная СУБД от Яндекса для аналитики в реальном времени. Одна из самых быстрых в своем классе.

-- ClickHouse
CREATE TABLE events (
    event_time DateTime,
    user_id UInt64,
    event_type String,
    amount Float64
) ENGINE = MergeTree()
ORDER BY (event_time, user_id);

-- Аналитический запрос
SELECT 
    toHour(event_time) AS hour,
    event_type,
    COUNT(*) AS events,
    SUM(amount) AS total_amount
FROM events
WHERE event_time >= '2024-01-01'
GROUP BY hour, event_type
ORDER BY hour ASC, total_amount DESC;

Характеристики ClickHouse:

  • Максимальная производительность аналитических запросов
  • Сжатие данных (обычно в 5-10 раз)
  • Вещественные таблицы (материализованные представления для агрегатов)
  • Распределенные запросы (кластер)
  • Поддержка SQL (с расширениями)

Когда использовать:

  • Аналитика в реальном времени (дашборды, метрики)
  • Логи и события (миллиарды строк)
  • Временные ряды (мониторинг, IoT)
  • Data Lake (ClickHouse как движок запросов)

Apache Cassandra

Cassandra — распределенная колоночная БД, спроектированная для высокой доступности и масштабируемости.

-- Cassandra (CQL)
CREATE TABLE users (
    user_id UUID PRIMARY KEY,
    name TEXT,
    email TEXT,
    age INT,
    city TEXT
);

-- Запрос (обязательно с partition key)
SELECT name, age FROM users WHERE user_id = 123;

Важное отличие Cassandra:

  • Cassandra использует колоночную модель внутри партиции, но строковую между партициями
  • Первичный ключ = partition key (распределение) + clustering columns (сортировка внутри партиции)
  • Запросы без partition key — полное сканирование кластера

Характеристики Cassandra:

  • Высокая доступность (AP по CAP-теореме)
  • Линейная масштабируемость (добавление узлов)
  • Отсутствие единой точки отказа
  • Настраиваемая согласованность

Когда использовать:

  • Высокая нагрузка на запись (логи, временные ряды)
  • Геораспределенные системы
  • Системы, где доступность важнее согласованности
  • Приложения с простыми паттернами доступа

Apache HBase

HBase — колоночная БД поверх HDFS (Hadoop Distributed File System).

Характеристики HBase:

  • Интеграция с экосистемой Hadoop (HDFS, MapReduce, Spark)
  • Сильная согласованность (CP по CAP-теореме)
  • Автоматическое шардирование
  • Массовая параллельная обработка

Когда использовать:

  • Большие данные (петабайты)
  • Batch-обработка через Spark/Hadoop
  • Системы, где согласованность важнее доступности

Apache Druid

Druid — колоночная БД для временных рядов и аналитики реального времени.

Характеристики Druid:

  • Временные ряды как основная модель
  • Вещественные агрегации (rollup)
  • Поддержка точных и приблизительных (approx) агрегаций
  • Индексация времени

Когда использовать:

  • Мониторинг и метрики
  • Ad-hoc аналитика по временным данным
  • Дашборды реального времени

Vertica, Redshift, BigQuery

Колоночные хранилища данных (Data Warehouses) от вендоров.

СистемаПлатформаОсобенность
VerticaOn-premise / CloudПроприетарная, очень быстрая
Amazon RedshiftCloud (AWS)Управляемое хранилище данных
Google BigQueryCloud (GCP)Серверлес, аналитика без администрирования
SnowflakeCloud (multi-cloud)Хранилище данных как сервис

Модель данных колоночных БД

Cassandra: Partition Key + Clustering Columns

Cassandra использует уникальную модель данных, которая часто сбивает с толку разработчиков, привыкших к SQL.

CREATE TABLE events (
    tenant_id UUID,           -- PARTITION KEY (распределение по узлам)
    event_date DATE,          -- CLUSTERING COLUMN 1
    event_hour INT,           -- CLUSTERING COLUMN 2
    event_type TEXT,          -- CLUSTERING COLUMN 3
    data TEXT,
    PRIMARY KEY ((tenant_id), event_date, event_hour, event_type)
);

Как это работает:

  1. Partition Key (tenant_id): Определяет, на каком узле кластера будут храниться данные. Все строки с одинаковым partition key хранятся на одном узле.
  2. Clustering Columns (event_date, event_hour, event_type): Определяют порядок строк внутри партиции (сортировка). Позволяют выполнять диапазонные запросы внутри партиции.

Эффективные запросы (используют partition key):

SELECT * FROM events WHERE tenant_id = 123;
SELECT * FROM events WHERE tenant_id = 123 AND event_date = '2024-01-01';
SELECT * FROM events WHERE tenant_id = 123 AND event_date > '2024-01-01';
SELECT * FROM events WHERE tenant_id = 123 AND event_date = '2024-01-01' AND event_hour = 12;

Неэффективные запросы (не используют partition key — scatter-gather):

SELECT * FROM events WHERE event_date = '2024-01-01';  -- Сканирование всего кластера!
SELECT * FROM events WHERE event_type = 'click';       -- Сканирование всего кластера!

ClickHouse: ORDER BY (без PRIMARY KEY)

В ClickHouse нет первичного ключа в традиционном смысле. ORDER BY определяет порядок сортировки данных на диске.

CREATE TABLE events (
    event_time DateTime,
    user_id UInt64,
    event_type String,
    amount Float64
) ENGINE = MergeTree()
ORDER BY (event_time, user_id);  -- Данные отсортированы по (event_time, user_id)

Особенности:

  • Данные физически сортируются по ORDER BY (это влияет на сжатие и скорость запросов)
  • Можно указать PRIMARY KEY отдельно (часть префикса ORDER BY)
  • Индексы разреженные (одна запись на 8192 строки)

Сжатие в колоночных БД

Сжатие — ключевой фактор производительности колоночных БД. Оно уменьшает объем данных на диске и ускоряет чтение (меньше данных нужно прочитать).

Алгоритмы сжатия

Dictionary encoding (кодирование словарем):

Исходные: Москва, СПб, Москва, СПб, Москва, СПб...
Словарь: {1: "Москва", 2: "СПб"}
Сжатые: [1,2,1,2,1,2...]

Delta encoding (кодирование разностями):

Исходные: 100, 101, 103, 106, 110, 115...
Разности: 100, +1, +2, +3, +4, +5...

Run-length encoding (кодирование длин серий):

Исходные: AAAABBBCCD
Сжатые: 4A,3B,2C,1D

Bit-packing (упаковка битов):

Если значения занимают только 10 битов, а не 32 — упаковываем их в 10 битов

Пример сжатия в ClickHouse

-- Создание таблицы с указанием кодеков сжатия
CREATE TABLE metrics (
    timestamp DateTime CODEC(Delta, LZ4),
    sensor_id UInt32 CODEC(T64, ZSTD),
    value Float64 CODEC(Gorilla, ZSTD)
) ENGINE = MergeTree()
ORDER BY timestamp;

Кодеки ClickHouse:

  • Delta: для монотонных последовательностей (времена)
  • DoubleDelta: для квадратичных последовательностей
  • Gorilla: для временных рядов (алгоритм Facebook)
  • T64: для целых чисел с небольшим диапазоном
  • LZ4, ZSTD: универсальное сжатие

Запросы и агрегации

ClickHouse: Аналитические запросы

-- Группировка с несколькими агрегатами
SELECT 
    toStartOfMonth(event_time) AS month,
    event_type,
    COUNT() AS events,
    uniq(user_id) AS unique_users,
    avg(amount) AS avg_amount,
    quantile(0.9)(amount) AS p90_amount
FROM events
WHERE event_time >= '2024-01-01'
GROUP BY month, event_type
WITH TOTALS  -- Итоговая строка по всем группам
ORDER BY month ASC, events DESC;

-- Оконные функции (с версии 21.3)
SELECT 
    event_time,
    user_id,
    amount,
    avg(amount) OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM events;

-- Материализованное представление (вещественная агрегация)
CREATE MATERIALIZED VIEW events_hourly
ENGINE = SummingMergeTree()
ORDER BY (hour, event_type)
AS SELECT
    toStartOfHour(event_time) AS hour,
    event_type,
    COUNT() AS events,
    sum(amount) AS total_amount
FROM events
GROUP BY hour, event_type;

Cassandra: Ограниченные запросы

Cassandra не поддерживает агрегации типа SUM/AVG на сервере (кроме COUNT). Агрегации нужно делать в приложении.

-- Разрешенные операции
SELECT COUNT(*) FROM events WHERE tenant_id = 123;  -- Да
SELECT * FROM events WHERE tenant_id = 123 LIMIT 100;  -- Да

-- Запрещенные операции
SELECT SUM(amount) FROM events WHERE tenant_id = 123;  -- НЕТ!
SELECT AVG(amount) FROM events WHERE tenant_id = 123;  -- НЕТ!
SELECT * FROM events WHERE tenant_id = 123 ORDER BY amount;  -- НЕТ!

Почему? Cassandra спроектирована для высокой доступности и масштабируемости, а не для аналитики.

Когда использовать колоночные БД

Идеальные сценарии

СценарийПочему подходит
Хранилище данных (Data Warehouse)Аналитические запросы на терабайты данных
Бизнес-интеллект (BI)Дашборды, отчеты, ad-hoc аналитика
Логи и событияМиллиарды записей, запросы за период
Временные рядыМетрики мониторинга, IoT, финансовые данные
A/B тестированиеАгрегация по группам
Рекомендательные системыАнализ поведения пользователей
Clickstream аналитикаАнализ действий пользователей на сайте

Сомнительные сценарии

СценарийПочему плохо подходит
OLTP-системы (интернет-магазин, банк)Плохая производительность одиночных INSERT/UPDATE
Системы с частыми обновлениямиКолоночные БД не любят UPDATE (особенно Cassandra)
Приложения с выборкой по одному IDОверхед на сборку строки из колонок
Данные с глубокой нормализациейКолоночные БД лучше работают с денормализованными данными
Графовые запросыЕсть специализированные инструменты (Neo4j)

Сравнение колоночных БД

ХарактеристикаClickHouseCassandraHBaseDruid
Основной сценарийOLAP-аналитикаВысокая доступность, записьБольшие данные (Hadoop)Временные ряды
Скорость чтенияОчень высокаяСредняяСредняяВысокая
Скорость записиВысокая (пакетная)Очень высокаяВысокаяВысокая (потоковая)
СжатиеОтличное (5-10x)ХорошееХорошееОтличное
SQL-поддержкаДа (расширенный SQL)CQL (SQL-like)НетНет (свой язык)
ТранзакцииНетНет (LWT ограничены)НетНет
Вторичные индексыДаОграниченноНетНет
МасштабированиеГоризонтальное (шардирование)Горизонтальное (DHT)Горизонтальное (HDFS)Горизонтальное
CAPCP (по умолчанию)AP (настраиваемая)CPCP
Сложность администрированияСредняяВысокаяВысокаяСредняя

Проектирование схемы для колоночных БД

ClickHouse: Выбор ORDER BY

ORDER BY — самое важное решение в ClickHouse. Оно определяет:

  • Порядок данных на диске
  • Эффективность сжатия
  • Какие запросы будут быстрыми

Правила выбора ORDER BY:

  1. Ставьте первыми столбцы, по которым чаще всего фильтруете.
  2. Ставьте первыми столбцы с высокой кардинальностью (много уникальных значений).
  3. Группируйте столбцы, которые часто используются вместе.
-- Хороший ORDER BY для таблицы событий
ORDER BY (event_date, tenant_id, user_id)

-- Плохой ORDER BY
ORDER BY (user_id, event_date)  -- user_id имеет высокую кардинальность, но фильтрация чаще по event_date

Cassandra: Выбор Partition Key

Partition Key — самое важное решение в Cassandra.

Правила выбора Partition Key:

  1. Обеспечьте равномерное распределение данных по узлам. Не используйте один ключ для всех данных.
  2. Избегайте “горячих” партиций. Не кладите слишком много данных в одну партицию (лимит ~100 МБ, но лучше <10 МБ).
  3. Группируйте данные, которые запрашиваются вместе. Все данные одной партиции хранятся на одном узле.
-- Хороший partition key для таблицы событий
PRIMARY KEY ((tenant_id, event_date), event_time, event_type)
-- Данные распределены по tenant_id + event_date (равномерно)

-- Плохой partition key
PRIMARY KEY ((status), created_at, id)
-- status имеет мало значений (active, pending, completed) → горячие партиции

Распространенные ошибки

Ошибка 1: Использование Cassandra как аналитической БД

Попытка выполнить SELECT SUM(amount) FROM huge_table в Cassandra.

Как исправить: Cassandra не для аналитики. Используйте ClickHouse, Druid или выгружайте данные в отдельную аналитическую систему.

Ошибка 2: Неправильный выбор partition key в Cassandra

-- Плохо
PRIMARY KEY ((status), created_at, id)  -- status: 'active', 'pending', 'completed'

Все активные записи попадают в одну партицию → горячая партиция, перегрузка одного узла.

Как исправить: Добавить в partition key столбец с высокой кардинальностью (tenant_id, user_id, date).

-- Хорошо
PRIMARY KEY ((tenant_id, status), created_at, id)

Ошибка 3: Игнорирование сжатия в ClickHouse

Без сжатия колоночная БД теряет половину преимуществ.

Как исправить: Используйте кодеки сжатия, особенно для монотонных последовательностей (Delta, DoubleDelta).

Ошибка 4: Частые UPDATE в колоночной БД

Колоночные БД не оптимизированы для UPDATE. Каждое обновление может вызвать перезапись больших блоков данных.

Как исправить: Используйте append-only модель (только INSERT). Обновления реализуйте как INSERT новой версии + фоновая очистка старых.

Ошибка 5: Выбор колоночной БД для OLTP

Попытка построить интернет-магазин на Cassandra или ClickHouse.

Как исправить: Для OLTP используйте реляционные (PostgreSQL) или документные (MongoDB) БД. Колоночные — для аналитики.

Резюме для системного аналитика

  1. Колоночные базы данных хранят данные по столбцам, а не по строкам. Это дает колоссальный выигрыш в производительности для аналитических запросов, которые читают много строк, но мало столбцов.

  2. Основные преимущества: чтение только нужных столбцов, лучшее сжатие (5-10x), векторизованная обработка (SIMD), пропуск блоков по метаданным.

  3. ClickHouse — король аналитики. Максимальная производительность для OLAP-запросов, отличное сжатие, поддержка SQL. Идеален для логов, событий, временных рядов.

  4. Cassandra — король высокой доступности. Линейная масштабируемость, отсутствие единой точки отказа, высокая скорость записи. Требует проектирования запросов заранее (no ad-hoc queries).

  5. Колоночные БД плохо подходят для OLTP. Одиночные INSERT медленнее, UPDATE проблемны, нет сложных транзакций.

  6. Проектирование схемы критически важно. В ClickHouse — выбор ORDER BY. В Cassandra — выбор partition key и clustering columns.

Проверка знаний

Вопрос 1 из 4
Что такое колоночная NoSQL/ширококолончатая БД?
Когда колоночные БД особенно уместны?
Почему колоночный подход может быть эффективным?
Что важно учитывать в колоночных БД?

Вопросы, где были ошибки