Настройка базы данных Cassandra для веб-приложения
Apache Cassandra — распределённая NoSQL база с линейным масштабированием записи и высокой доступностью без единой точки отказа. Проектируется не вокруг нормализации данных, а вокруг запросов: сначала определяешь, как будут читаться данные, потом строишь схему. Это меняет подход к проектированию кардинально.
Когда выбирают Cassandra
Временные ряды с миллионами событий в секунду, ленты активности, системы логирования, IoT-телеметрия — там, где нужно писать быстро и много. Netflix, Discord, Apple используют Cassandra именно для этого. Discord хранит триллионы сообщений в Cassandra. Для OLTP с сложными транзакциями — не подходит.
Установка Cassandra 4.1
echo "deb https://debian.cassandra.apache.org 41x main" > /etc/apt/sources.list.d/cassandra.sources.list
curl https://downloads.apache.org/cassandra/KEYS | apt-key add -
apt update && apt install -y cassandra
cassandra.yaml — ключевые параметры
cluster_name: 'MyAppCluster'
# Сеть
listen_address: 10.0.0.1 # IP текущего узла
rpc_address: 10.0.0.1
seeds: "10.0.0.1,10.0.0.2,10.0.0.3"
# Директории
data_file_directories:
- /var/lib/cassandra/data
commitlog_directory: /var/lib/cassandra/commitlog # отдельный диск для скорости
hints_directory: /var/lib/cassandra/hints
saved_caches_directory: /var/lib/cassandra/saved_caches
# Производительность
concurrent_reads: 32
concurrent_writes: 32
concurrent_counter_writes: 16
memtable_heap_space: 2048 # МБ
compaction_throughput_mb_per_sec: 64
# Репликация и консистентность
endpoint_snitch: GossipingPropertyFileSnitch
# JVM
num_tokens: 256
JVM настройки
# /etc/cassandra/jvm11-server.options
-Xms8G
-Xmx8G
-XX:+UseG1GC
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:MaxGCPauseMillis=300
-XX:InitiatingHeapOccupancyPercent=70
Схема данных — Query-driven design
-- Keyspace с репликацией
CREATE KEYSPACE myapp
WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3
} AND durable_writes = true;
USE myapp;
-- Лента событий пользователя
-- Запрос: все события пользователя за период, отсортированные по времени
CREATE TABLE user_events (
user_id uuid,
occurred_at timestamp,
event_id uuid,
event_type text,
payload text, -- JSON
PRIMARY KEY ((user_id), occurred_at, event_id)
) WITH CLUSTERING ORDER BY (occurred_at DESC)
AND compaction = {'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 7}
AND default_time_to_live = 7776000; -- 90 дней
-- Статистика по пользователям
CREATE TABLE user_stats (
user_id uuid PRIMARY KEY,
total_orders counter,
total_spent counter,
last_active timestamp
);
-- Временные ряды метрик
CREATE TABLE metrics (
service text,
bucket timestamp, -- округление до часа/дня
metric_name text,
ts timestamp,
value double,
PRIMARY KEY ((service, bucket, metric_name), ts)
) WITH CLUSTERING ORDER BY (ts DESC)
AND compaction = {'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'HOURS',
'compaction_window_size': 1};
Работа с данными из Node.js
import cassandra from 'cassandra-driver'
const client = new cassandra.Client({
contactPoints: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
localDataCenter: 'dc1',
keyspace: 'myapp',
credentials: { username: 'cassandra', password: process.env.CASSANDRA_PASSWORD! },
pooling: {
coreConnectionsPerHost: {
[cassandra.types.distance.local]: 3,
[cassandra.types.distance.remote]: 1
}
},
socketOptions: { readTimeout: 12000 }
})
await client.connect()
// Подготовленные запросы — обязательны в production
const insertEvent = await client.prepare(`
INSERT INTO user_events (user_id, occurred_at, event_id, event_type, payload)
VALUES (?, ?, ?, ?, ?)
`)
const selectEvents = await client.prepare(`
SELECT * FROM user_events
WHERE user_id = ? AND occurred_at >= ? AND occurred_at <= ?
ORDER BY occurred_at DESC
LIMIT ?
`)
// Batch-запись событий
async function writeEvents(events: UserEvent[]) {
const batch = events.map(e => ({
query: insertEvent,
params: [
cassandra.types.Uuid.fromString(e.userId),
new Date(e.occurredAt),
cassandra.types.TimeUuid.now(),
e.eventType,
JSON.stringify(e.payload)
]
}))
await client.batch(batch, { prepare: true, logged: false })
}
// Чтение с пагинацией
async function* fetchEvents(userId: string, from: Date, to: Date) {
const options = {
prepare: true,
fetchSize: 1000 // страница
}
let pageState: Buffer | undefined
do {
const result = await client.execute(
selectEvents,
[cassandra.types.Uuid.fromString(userId), from, to, 1000],
{ ...options, pageState }
)
yield result.rows
pageState = result.pageState as Buffer | undefined
} while (pageState)
}
Мониторинг и диагностика
# Статус кластера
nodetool status
# Нагрузка на узел
nodetool tpstats
nodetool cfstats myapp.user_events
# Медленные запросы (включить в cassandra.yaml)
# slow_query_log_timeout_in_ms: 500
# Compaction статус
nodetool compactionstats
# Очистка ненужных данных
nodetool cleanup myapp
Репликация и консистентность
// Разные уровни консистентности для разных операций
const { types: { consistencies } } = cassandra
// Запись — LOCAL_QUORUM для баланса скорости и надёжности
await client.execute(insertEvent, params, {
consistency: consistencies.localQuorum
})
// Чтение аналитики — ONE для максимальной скорости
await client.execute(selectEvents, params, {
consistency: consistencies.one
})
// Критические данные — QUORUM
await client.execute(criticalQuery, params, {
consistency: consistencies.quorum
})
Сроки
Настройка трёхузлового кластера Cassandra с базовой схемой: 3–4 дня. Проектирование схемы под конкретные запросы приложения + интеграция с бэкендом + нагрузочное тестирование: 1–2 недели. Миграция данных из реляционной базы в Cassandra с трансформацией модели: 2–4 недели.







