Колоночные
Колоночные базы данных — физическое хранение данных по столбцам, а не строкам. Преимущества для 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). Данные на диске хранятся последовательно: сначала все поля первой строки, потом все поля второй строки, и так далее.
Таблица “Сотрудники”:
| id | name | age | city | salary |
|---|---|---|---|---|
| 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, ZSTD | 3: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) от вендоров.
| Система | Платформа | Особенность |
|---|---|---|
| Vertica | On-premise / Cloud | Проприетарная, очень быстрая |
| Amazon Redshift | Cloud (AWS) | Управляемое хранилище данных |
| Google BigQuery | Cloud (GCP) | Серверлес, аналитика без администрирования |
| Snowflake | Cloud (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)
);Как это работает:
- Partition Key (
tenant_id): Определяет, на каком узле кластера будут храниться данные. Все строки с одинаковым partition key хранятся на одном узле. - 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,1DBit-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) |
Сравнение колоночных БД
| Характеристика | ClickHouse | Cassandra | HBase | Druid |
|---|---|---|---|---|
| Основной сценарий | OLAP-аналитика | Высокая доступность, запись | Большие данные (Hadoop) | Временные ряды |
| Скорость чтения | Очень высокая | Средняя | Средняя | Высокая |
| Скорость записи | Высокая (пакетная) | Очень высокая | Высокая | Высокая (потоковая) |
| Сжатие | Отличное (5-10x) | Хорошее | Хорошее | Отличное |
| SQL-поддержка | Да (расширенный SQL) | CQL (SQL-like) | Нет | Нет (свой язык) |
| Транзакции | Нет | Нет (LWT ограничены) | Нет | Нет |
| Вторичные индексы | Да | Ограниченно | Нет | Нет |
| Масштабирование | Горизонтальное (шардирование) | Горизонтальное (DHT) | Горизонтальное (HDFS) | Горизонтальное |
| CAP | CP (по умолчанию) | AP (настраиваемая) | CP | CP |
| Сложность администрирования | Средняя | Высокая | Высокая | Средняя |
Проектирование схемы для колоночных БД
ClickHouse: Выбор ORDER BY
ORDER BY — самое важное решение в ClickHouse. Оно определяет:
- Порядок данных на диске
- Эффективность сжатия
- Какие запросы будут быстрыми
Правила выбора ORDER BY:
- Ставьте первыми столбцы, по которым чаще всего фильтруете.
- Ставьте первыми столбцы с высокой кардинальностью (много уникальных значений).
- Группируйте столбцы, которые часто используются вместе.
-- Хороший ORDER BY для таблицы событий
ORDER BY (event_date, tenant_id, user_id)
-- Плохой ORDER BY
ORDER BY (user_id, event_date) -- user_id имеет высокую кардинальность, но фильтрация чаще по event_dateCassandra: Выбор Partition Key
Partition Key — самое важное решение в Cassandra.
Правила выбора Partition Key:
- Обеспечьте равномерное распределение данных по узлам. Не используйте один ключ для всех данных.
- Избегайте “горячих” партиций. Не кладите слишком много данных в одну партицию (лимит ~100 МБ, но лучше <10 МБ).
- Группируйте данные, которые запрашиваются вместе. Все данные одной партиции хранятся на одном узле.
-- Хороший 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) БД. Колоночные — для аналитики.
Резюме для системного аналитика
Колоночные базы данных хранят данные по столбцам, а не по строкам. Это дает колоссальный выигрыш в производительности для аналитических запросов, которые читают много строк, но мало столбцов.
Основные преимущества: чтение только нужных столбцов, лучшее сжатие (5-10x), векторизованная обработка (SIMD), пропуск блоков по метаданным.
ClickHouse — король аналитики. Максимальная производительность для OLAP-запросов, отличное сжатие, поддержка SQL. Идеален для логов, событий, временных рядов.
Cassandra — король высокой доступности. Линейная масштабируемость, отсутствие единой точки отказа, высокая скорость записи. Требует проектирования запросов заранее (no ad-hoc queries).
Колоночные БД плохо подходят для OLTP. Одиночные INSERT медленнее, UPDATE проблемны, нет сложных транзакций.
Проектирование схемы критически важно. В ClickHouse — выбор
ORDER BY. В Cassandra — выбор partition key и clustering columns.