Налаштування Apache 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
Схема даних — проектування на основі запитів
-- 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 ?
`)
// Пакетний запис подій
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
# Статус компакції
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 дні. Проектування схеми для конкретних запитів додатку + інтеграція з backend + тестування навантаження: 1–2 тижні. Міграція даних з реляційної бази в Cassandra з трансформацією моделі: 2–4 тижні.







