הגדרת Apache Cassandra עבור יישום web כוללת יותר מסתם התקנת חבילה. ללא עיצוב סכמה נכון ותצורת cluster, תיתקל בשאילתות איטיות, מחיצות חמות, ופעילות לא יציבה תחת עומס. במשך 5 שנים, הגדרנו את Cassandra עבור 30+ פרויקטים: זרמי אירועים, מדדים, ולוגים. הטעות הנפוצה ביותר היא מפתח ראשי שגוי. התעלמות מ-compaction וזיכרון לא מספק עבור memtables מובילה לירידת ביצועים של פי 2–3. הגדרת Cassandra נכונה מההתחלה חוסכת שבועות של עבודה חוזרת. הנה הטכניקות המוכחות שלנו.
היכן Cassandra חיונית
סדרות זמן עם מיליוני אירועים בשנייה, פידים של פעילות, מערכות לוגינג, טלמטריה של IoT — תרחישים הדורשים כתיבות מהירות ובנפח גבוה. Netflix, Discord, ו-Apple משתמשות ב-Cassandra בדיוק לשם כך. Discord מאחסנת טריליוני הודעות. עבור OLTP עם טרנזקציות מורכבות, היא אינה מתאימה. אנו משתמשים ב-Cassandra לאחסון מדדים ולוגים: היא מספקת סקלביליות כתיבה לינארית וסובלנות לתקלות ללא נקודת כשל יחידה.
כיצד להתקין ולהגדיר את Cassandra 4.1
התקנת 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לאחר ההתקנה, ערוך את 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 :
---
cluster_name: 'MyAppCluster' # Сеть
listen_address: 10.0.0.1
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 (в jvm11-server.options)
num_tokens: 256
בנוסף, הגדר את JVM כדי למנוע השהיות GC ארוכות:
# /etc/cassandra/jvm11-server.options
-Xms8G
-Xmx8G
-XX:+UseG1GC
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:MaxGCPauseMillis=300
-XX:InitiatingHeapOccupancyPercent=70 השוואת אסטרטגיות compaction
| אסטרטגיה | מטרה | מתי להשתמש |
|---|---|---|
| SizeTieredCompactionStrategy | ברירת מחדל | אפשרות אוניברסלית, מתאימה לרוב המקרים |
| TimeWindowCompactionStrategy | סדרות זמן | נתונים עם TTL, פידים של אירועים — מפחיתה את מספר הקבצים |
| LeveledCompactionStrategy | עדכונים תכופים | מערכות עם כתיבות מרובות, דורשת יותר שטח דיסק |
בחירת ה-compaction משפיעה ישירות על ביצועי הקריאה והכתיבה. לדוגמה, TimeWindowCompactionStrategy מפחיתה את מספר ה-SSTables פי 2–3 בהשוואה ל-SizeTieredCompactionStrategy בעבודה עם סדרות זמן. אנו ממליצים על TimeWindowCompactionStrategy עבור נתונים עם TTL, ועל LeveledCompactionStrategy עבור תרחישים עם עדכונים אינטנסיביים.
באיזו אסטרטגיית compaction לבחור?
בסס את הבחירה על מאפייני הנתונים. אם לנתונים יש TTL והם נכתבים בקצב קבוע — TimeWindowCompactionStrategy. עבור עדכונים ומחיקות תכופים — LeveledCompactionStrategy. SizeTieredCompactionStrategy היא בחירה בטוחה אם אינך בטוח.
מדוע בחירת מפתח מחיצה נכון חשובה?
מפתח המחיצה קובע את התפלגות הנתונים בין הצמתים. אם נבחר בצורה שגויה, חלק מהצמתים יהיו עמוסים בעוד אחרים יישארו ריקים. לדוגמה, עבור הטבלה cassandra.yaml אנו משתמשים ב-cluster_name: 'MyAppCluster' # Сеть listen_address: 10.0.0.1 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 (в jvm11-server.options) num_tokens: 256 — זה מבטיח התפלגות אחידה. עבור סדרות זמן (טבלה # /etc/cassandra/jvm11-server.options -Xms8G -Xmx8G -XX:+UseG1GC -XX:G1RSetUpdatingPauseTimePercent=5 -XX:MaxGCPauseMillis=300 -XX:InitiatingHeapOccupancyPercent=70 ), מפתח מורכב user_events מקבץ מדדים לפי שירות וחלון זמן. לעולם אל תשתמש בעמודות עם ערכים ייחודיים מעטים (boolean או enum) — זה מוביל למחיצות חמות.
סכמת נתונים — עיצוב מונחה שאילתות
-- 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,
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;
-- Статистика по пользователям
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}; אינטגרציה עם backend של 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()
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)
} רמות עקביות
| רמה | מהירות | אמינות | מקרה שימוש |
|---|---|---|---|
| ONE | מהירה | נמוכה | אנליטיקה, cache |
| LOCAL_QUORUM | בינונית | גבוהה | פעולות כתיבה |
| QUORUM | איטית | מקסימלית | נתונים קריטיים |
const { types: { consistencies } } = cassandra
await client.execute(insertEvent, params, { consistency: consistencies.localQuorum })
await client.execute(selectEvents, params, { consistency: consistencies.one })
await client.execute(criticalQuery, params, { consistency: consistencies.quorum }) ניטור ואבחון
nodetool status nodetool tpstats nodetool cfstats myapp.user_events nodetool compactionstats nodetool cleanup myapp הפעל רישום שאילתות איטיות ב-user_id: metrics.
טעויות נפוצות והפתרונות שלהן
- מחיצות חמות עקב מפתח שגוי: השתמש במפתחות מורכבים עם קרדינליות גבוהה. הימנע מעמודות עם ערכים מעטים.
-
צריכת זיכרון גבוהה: הפחת את
(service, bucket, metric_name)או עבור מ-G1GC ל-ParallelGC. -
כתיבות איטיות: בדוק את הדיסק עבור commitlog — השתמש ב-SSD נפרד. הגדל את
-- 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, 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; -- Статистика по пользователям 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};.
כמה זמן אורך הגדרה מלאה?
פרויקט טיפוסי עם שלושה צמתים ואינטגרציה אורך 2–3 שבועות. זה כולל ביקורת דרישות, עיצוב סכמה, פריסת cluster, כוונון תצורה, וכתיבת adapter ל-backend. Clusters מורכבים עם מרכזי נתונים מרובים עשויים לארוך עד 5 שבועות. קבל הערכה מדויקת לפרויקט שלך — צור קשר.
המקרים והערבויות שלנו
השלמנו מעל 30 פרויקטים עם Cassandra, כולל מערכות מדדים לפלטפורמת פרסום (100 מיליון אירועים ביום) ופידים של פעילות עבור שירות SaaS. בכל פרויקט, אנו מבטיחים פעילות cluster יציבה למשך חודש לאחר המסירה. למהנדסים שלנו יש ניסיון של 5+ שנים עם Cassandra וטכנולוגיות קשורות. צור קשר לייעוץ — נדון בארכיטקטורה שלך ללא עלות.
מה כלול בהגדרה turnkey
- ביקורת הארכיטקטורה הנוכחית ודרישות העומס
- עיצוב סכמת נתונים (עיצוב מונחה שאילתות)
- פריסת cluster (bare-metal / cloud / Docker)
- אופטימיזציה של
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() 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) }, JVM, והגדרות רשת - אינטגרציית backend (Node.js, Python, Go)
- תיעוד סכמה והוראות תפעול
- הדרכת צוות והמלצות תפעוליות
- פעילות cluster יציבה מובטחת למשך חודש לאחר המסירה
הזמן כוונון Cassandra לפרויקט שלך — קבל ייעוץ ממהנדס עם 5 שנות ניסיון.







