Первые шаги с Cassandra
Разработчику
Аналитику
Тестировщику
Архитектору
Инженеру
Установка системы Apache Cassandra
Процесс установки требует наличия среды выполнения Java (JDK), так как Cassandra написана на Java. Для работы необходим серверный компонент (cassandra) и клиентские утилиты (cqlsh).
Установка программ обычно выглядит так (сначала JDK, затем пакет Cassandra — или один корпоративный мастер):
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Требования к системе
- Операционная система: Linux (рекомендуется Ubuntu/CentOS), macOS или Windows.
- Ява (JDK): Версия 8, 11 или 17 (в зависимости от версии Cassandra).
- Права администратора на машине.
- Минимум 2 ГБ оперативной памяти.
- Доступ к интернету для загрузки пакетов.
Алгоритм установки
Вариант А — Установка на Linux (на примере Ubuntu/Debian)
На Ubuntu/Debian:
sudo apt update
sudo apt install openjdk-11-jdk
echo "deb http://www.apache.org/dist/cassandra/debian 41x main" | sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list
curl https://www.apache.org/dist/cassandra/KEYS | sudo apt-key add -
sudo apt-get update
sudo apt-get install cassandra
Примечание:
apt-keyустарел; для production используйте подписанныйdeb822-репозиторий или Docker (вариант Б ниже). Для локального обучения команды выше достаточно.
Проверка службы:
sudo systemctl status cassandra
Интерактивная оболочка:
cqlsh
Вариант Б — Использование Docker (универсальный способ)
Создание контейнера позволяет быстро развернуть окружение без сложной настройки зависимостей:
docker run --name my-cassandra \
-d \
-p 9042:9042 \
-p 7000:7000 \
-p 7001:7001 \
-e CASSANDRA_CLUSTER_NAME='MyCluster' \
cassandra:latest
Подключение к контейнеру:
docker exec -it my-cassandra cqlsh
Создание ключевых пространств (Keyspace)
В Cassandra аналогом базы данных является keyspace (ключевое пространство). Оно определяет параметры репликации и стратегию хранения данных. В отличие от SQL, создание keyspace обязательно перед созданием таблиц.
Командный способ
В cqlsh:
CREATE KEYSPACE company_db
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 1
};
Примечание: replication_factor — число копий данных в кластере (1 — только тест, в production обычно 3; см. блок CREATE KEYSPACE выше).
Выбор стратегии репликации
| Стратегия | Описание | Сценарий использования |
|---|---|---|
| SimpleStrategy | Реплицирует данные на соседние узлы в порядке их следования. | Одноразовые кластеры, тестовые среды. |
| NetworkTopologyStrategy | Позволяет контролировать репликацию по зонам availability (AZ) и регионам. | Продвинутые кластеры, требующие отказоустойчивости. |
Переключение контекста на созданное пространство:
USE company_db;
Создание таблицы и первичного ключа
Таблица в Cassandra — это логическая структура, но её проектирование критически зависит от модели запросов. Данные не хранятся в произвольном порядке, а сортируются согласно Первичному ключу.
Синтаксис создания таблицы
Первичный ключ состоит из Partition Key (ключ партиции) и Clustering Columns (кластеризующие колонки).
CREATE TABLE employees (
employee_id UUID PRIMARY KEY,
first_name TEXT,
last_name TEXT,
email TEXT,
hire_date TIMESTAMP,
salary DECIMAL,
department_id INT
);
Анализ компонентов определения
- employee_id: Тип
UUIDгарантирует уникальность без внешних генераторов. Это Partition Key. Все данные с одним ID будут находиться на одном узле. - first_name, last_name, email: Текстовые поля (
TEXT). - hire_date: Тип
TIMESTAMPдля хранения даты и времени. - salary: Тип
DECIMALдля точных финансовых вычислений. - department_id: Целочисленное поле.
Проектирование Первичного ключа
В Cassandra нельзя писать SELECT * WHERE column != partition_key. Запросы всегда должны начинаться с Partition Key. Сводная таблица "запрос приложения → PRIMARY KEY" — в главе Cassandra.
Пример таблицы с составным ключом для частого поиска по отделу:
CREATE TABLE employees_by_dept (
department_id INT,
employee_id UUID,
first_name TEXT,
last_name TEXT,
salary DECIMAL,
PRIMARY KEY (department_id, employee_id)
);
Здесь department_id — Partition Key, а employee_id — Clustering Column. Данные внутри отдела будут отсортированы по ID сотрудника.
Добавление ограничений и индексов
Cassandra имеет строгие ограничения целостности. Внешние ключи не поддерживаются напрямую. Индексы существуют, но их использование ограничено.
Ограничения уникальности
Уникальность обеспечивается только частью Первичного ключа. Если попытаться вставить дубликат employee_id, возникнет ошибка.
Для обеспечения уникальности других полей (например, email) необходимо создать отдельную таблицу-справочник:
CREATE TABLE emails_lookup (
email TEXT PRIMARY KEY,
employee_id UUID
);
Создание индексов
Вторичный индекс в CQL создаётся только по одному столбцу (составных индексов, как в SQL, нет). Индекс не заменяет partition key: запрос без partition key разносится по всем узлам кластера.
Создание индекса по фамилии:
CREATE INDEX idx_employees_last_name ON employees(last_name);
Запрос по индексу без partition key (допустимо для отладки, не для production):
SELECT * FROM employees WHERE last_name = 'Ivanov';
Важно: такой запрос выполняет fan-out на каждый узел. Для частого поиска по email или last_name лучше отдельная lookup-таблица с нужным partition key (см. emails_lookup выше), а не вторичный индекс.
Выполнение CRUD операций
CRUD в Cassandra реализован через язык CQL (Cassandra Query Language).
Что можно в WHERE:
- обязательно — полный partition key (все его столбцы);
- опционально — clustering columns (равенство и диапазоны, только слева направо);
- фильтр по неключевому столбцу — только через вторичный индекс + fan-out или
ALLOW FILTERING(отладка).
Подробнее: таблица проектирования.
Создание записей (Create)
Вставка нового сотрудника:
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, salary, department_id)
VALUES (uuid(), 'Ivan', 'Ivanov', 'ivanov@example.com', toTimestamp(now()), 75000.00, 1);
Вставка с явным указанием UUID:
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, salary, department_id)
VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Maria', 'Petrova', 'petrova@example.com', '2024-02-10', 82000.00, 2);
Lightweight transaction (вставка только если строки с таким ключом ещё нет):
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, salary, department_id)
VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Alexey', 'Sidorov', 'sidorov@example.com', toTimestamp(now()), 65000.00, 1)
IF NOT EXISTS;
Требует согласованности SERIAL / LOCAL_SERIAL — медленнее обычной вставки. С uuid() в ключе смысл IF NOT EXISTS теряется: каждая вставка создаёт новый ключ.
Чтение данных (Read)
Выборка конкретного сотрудника по ID (самый быстрый запрос):
SELECT * FROM employees WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
Выборка всех сотрудников определенного отдела (требуется указание Partition Key):
SELECT * FROM employees_by_dept WHERE department_id = 1;
Сортировка результатов внутри партиции:
SELECT * FROM employees_by_dept WHERE department_id = 1 ORDER BY employee_id DESC;
Обновление данных (Update)
Изменение зарплаты сотрудника:
UPDATE employees SET salary = 80000.00 WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
Обновление с проверкой условия (lightweight transaction — условие только в IF, не в WHERE):
UPDATE employees SET salary = 85000.00
WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
IF salary < 80000;
Ответ сервера: [applied] true или false с текущими значениями столбцов из IF.
Удаление данных (Delete)
Удаление конкретного сотрудника:
DELETE FROM employees WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
Удаление конкретной колонки:
DELETE email FROM employees WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
Очистка всей таблицы (осторожно!):
TRUNCATE TABLE employees;
Дополнительные таблицы под другие запросы
В Cassandra нет SQL-VIEW и нет JOIN. Вместо одной "универсальной" таблицы создают несколько таблиц с разным PRIMARY KEY под разные запросы (см. таблицу проектирования).
Двухшаговый lookup по email (паттерн "запрос → ключ → основная таблица"):
INSERT INTO emails_lookup (email, employee_id)
VALUES ('ivanov@example.com', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11');
-- Шаг 1: partition key = email
SELECT employee_id FROM emails_lookup WHERE email = 'ivanov@example.com';
-- Шаг 2: partition key = employee_id
SELECT * FROM employees WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
В приложении те же два запроса выполняет код (или batch из двух prepared statements).
Materialized Views (legacy, не использовать в новых проектах)
В Cassandra 3.x были Materialized Views — автоматические копии таблицы под другим ключом. С 4.0 они deprecated, в 5.0 удалены. Причины отказа — задержки синхронизации, лишняя нагрузка на запись, сложное сопровождение.
В legacy-кластере 3.x синтаксис выглядел так:
CREATE MATERIALIZED VIEW employees_by_email AS
SELECT employee_id, first_name, last_name, email, salary
FROM employees
WHERE email IS NOT NULL AND employee_id IS NOT NULL
PRIMARY KEY (email, employee_id);
Современная замена: явная денормализованная таблица (emails_lookup + employees) и обновление в приложении или через поток событий (Kafka и т.п.).
Агрегация и эмуляция JOIN
В Cassandra отсутствуют хранимые процедуры, триггеры и операторы JOIN в классическом понимании. Логика агрегации и соединения данных реализуется на уровне приложения.
Функция с агрегатными функциями
Агрегация возможна только внутри одной партиции. Для расчета средней зарплаты в отделе используется запрос к таблице, где department_id является Partition Key.
SELECT AVG(salary) as avg_salary
FROM employees_by_dept
WHERE department_id = 1;
Результат будет вычислен на стороне сервера Cassandra, но только для данных в пределах указанной партиции.
Эмуляция JOIN через приложение
Cassandra не поддерживает JOIN между таблицами. Для получения данных из разных таблиц (например, сотрудники и отделы) необходимо выполнить два отдельных запроса и объединить результаты в коде приложения.
Шаг 1: Получение списка отделов (IN по partition key читает несколько партиций — допустимо для малого числа id):
SELECT id, name FROM departments WHERE id IN (1, 2);
Шаг 2: Получение сотрудников (отдельный запрос на каждый отдел или IN по partition key):
SELECT * FROM employees_by_dept WHERE department_id IN (1, 2);
Шаг 3: Объединение в коде (Python пример)
departments = {d['id']: d['name'] for d in dept_rows}
for emp in emp_rows:
emp['dept_name'] = departments.get(emp['department_id'])
Валидация данных (без триггеров)
Прямых триггеров в Cassandra нет. Проверка условий:
- На стороне приложения — перед отправкой запроса.
- Lightweight transactions (LWT) —
IF NOT EXISTS/IF col = valвINSERT/UPDATE/DELETE(медленно, только для критичных проверок). - UDF (User Defined Functions) — только в Cassandra 3.x; удалены в 4.0+. В legacy-кластерах функция могла вычислять значение, но не блокировать вставку.
Пример LWT для уникальности email (предпочтительный способ):
INSERT INTO emails_lookup (email, employee_id)
VALUES ('new@example.com', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')
IF NOT EXISTS;
Условное обновление зарплаты (см. раздел Update выше):
UPDATE employees SET salary = 85000.00
WHERE employee_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
IF salary < 80000;
Отрицательную зарплату отсекайте в коде приложения — CQL не проверяет CHECK-ограничения, как SQL.
Сквозной практикум (15 минут)
После Docker из раздела установки или локального cqlsh:
Код ITЗагрузка примера кода…
Проверьте, что запрос всегда содержит device_id (partition key). Запрос только WHERE recorded_at > … без device_id потребует ALLOW FILTERING и не подходит для production.
Очистка:
DROP KEYSPACE IF EXISTS learning_cassandra;
Теория: Cassandra, уровни консистентности — там же.
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Первые шаги (маршрут подборки) — Первые шаги с Redis, Первые шаги с Memcached, Первые шаги с MongoDB, Первые шаги к микросервисам, Первые шаги с SQL, Первые шаги с Docker и Kubernetes.