Распределенные базы данных существуют в условиях постоянной неопределенности. Узлы выходят из строя, сетевые соединения рвутся, а данные все равно должны оставаться доступными и согласованными. Apache Cassandra решает эту задачу через комбинацию механизмов восстановления, стратегии распределения токенов и алгоритмов уплотнения данных. Понимание этих процессов отделяет поверхностное знакомство от глубокой экспертизы в работе с Cassandra.

Природа проблемы согласованности

Каждая распределенная система сталкивается с фундаментальной проблемой. Узел недоступен три часа, пропускает сотни тысяч записей. Система hints пытается компенсировать потери, но работает с ограничениями. По умолчанию hints хранятся всего три часа, определяемые параметром max_hint_window_in_ms в cassandra.yaml. Если узел вернется через пять часов, hints уже не помогут.

Чтения и записи с низкими уровнями consistency усугубляют ситуацию. Запись с уровнем ONE может попасть только на один узел из трех реплик. Tombstones могут исчезнуть с одной реплики раньше, чем с другой, если gc_grace_seconds истек до завершения синхронизации. Результат? Удаленные данные возвращаются как зомби.

Процесс repair восстанавливает согласованность, сравнивая данные между репликами. Cassandra строит деревья Меркла для каждого диапазона токенов, хеширует данные и находит расхождения без передачи всех данных по сети. Координирующий узел запрашивает validation compaction на каждой реплике, получает деревья обратно, сравнивает их и инициирует streaming несогласованных данных.

Базовый запуск repair охватывает все keyspaces:

nodetool repair

Эта команда обрабатывает все диапазоны токенов, реплицированные на узле. Проблема в том, что при запуске на каждом узле происходит избыточная работа. Диапазон, хранящийся на узлах A, B и C, будет восстановлен трижды. Флаг -pr решает это, ограничивая repair только первичными диапазонами:

nodetool repair -pr

Запуск этой команды последовательно на каждом узле датацентра восстанавливает весь кластер без дублирования. Для конкретного keyspace:

nodetool repair -pr my_keyspace

Для отдельной таблицы внутри keyspace:

nodetool repair -pr my_keyspace my_table

Мониторинг процесса repair доступен через несколько команд. Просмотр активных сессий:

nodetool repair_admin list

Принудительная остановка зависшей сессии:

nodetool repair_admin cancel <repair_id>

Проверка pending compactions после repair:

nodetool compactionstats

Инкрементальное восстановление против полного

Две философии восстановления существуют в Cassandra. Полное восстановление проверяет все данные, независимо от того, восстанавливались они ранее или нет. Инкрементальное работает только с новыми или измененными данными.

Инкрементальный режим по умолчанию активен в Cassandra начиная с версии 2.2. Принудительный запуск полного восстановления:

nodetool repair -full

Инкрементальное восстановление помечает SSTables как "repaired" после успешной синхронизации. Процесс anti-compaction разделяет SSTable на два файла: один содержит восстановленные данные с меткой времени repair, другой содержит невосстановленные данные. Следующий запуск repair проверяет только непомеченные файлы.

Миграция кластера на инкрементальный режим требует осторожности. Процесс для одного узла:

# Отключить автоматическое уплотнение
nodetool disableautocompaction

# Запустить полное последовательное восстановление
nodetool repair -full

# Остановить узел
nodetool drain
nodetool stop

# Пометить существующие SSTables как восстановленные
sstablerepairedset --really-set --is-repaired <sstable_files>

# Запустить узел
cassandra

Инкрементальный подход создает проблемы при высокой частоте записей. Множество мелких SSTables накапливаются, создавая нагрузку на compaction. Если pending compactions растут быстрее, чем система успевает их обрабатывать, производительность падает.

Параллельное и последовательное восстановление определяют стратегию построения деревьев Меркла. Последовательный режим обрабатывает реплики по очереди:

nodetool repair -seq

Параллельный режим обрабатывает все реплики одновременно, ускоряя процесс, но создавая нагрузку на все узлы:

nodetool repair -par

Datacenter-aware режим комбинирует подходы, запуская последовательное восстановление в каждом датацентре параллельно:

nodetool repair -dcpar

Subrange repair позволяет восстанавливать конкретные диапазоны токенов. Это требует знания границ:

nodetool repair -st <start_token> -et <end_token>

Получение списка токенов для узла:

nodetool ring

Определение endpoint для конкретного ключа:

nodetool getendpoints my_keyspace my_table "user123"

Архитектура распределения токенов

Cassandra организует данные через consistent hashing. Каждый ключ партиции хешируется в значение токена, определяющее размещение данных. Муrmur3Partitioner использует 64-битное пространство от -2^63 до 2^63-1.

Для кластера из четырех узлов без vnodes распределение выглядит так:

Node 1: -9223372036854775808 to -4611686018427387904
Node 2: -4611686018427387903 to -1
Node 3: 0 to 4611686018427387903
Node 4: 4611686018427387904 to 9223372036854775807

Partition key "user:12345" хешируется, например, в токен 3500000000000000000, попадая на Node 3 и его реплики согласно replication factor.

Виртуальные узлы (vnodes) разбивают каждый физический узел на множество маленьких диапазонов. Параметр num_tokens в cassandra.yaml управляет количеством:

num_tokens: 256

При num_tokens=256 каждый физический узел владеет 256 небольшими диапазонами, разбросанными по всему кольцу. Добавление нового узла забирает маленькие куски от многих существующих узлов вместо перебалансировки одного большого диапазона.

Просмотр фактического распределения токенов:

nodetool ring

Вывод показывает каждый токен, владеющий узел и статус. Для анализа конкретной таблицы:

nodetool tablestats my_keyspace.my_table

Streaming процесс передает данные между узлами при балансировке. Мониторинг активных потоков:

nodetool netstats

Команда показывает источник, назначение, объем передаваемых данных и прогресс. При добавлении узла в кластер из 12 узлов с num_tokens=256, новый узел получит около 256 диапазонов от разных источников. Каждый существующий узел отдаст приблизительно 21 диапазон (256/12).

Проверка общего статуса кластера:

nodetool status

Вывод включает столбцы Status, State, Address, Load, Tokens, Owns и Host ID. Status показывает Up или Down, State показывает Normal, Joining, Leaving или Moving. Load отражает объем данных на диске, включая неочищенные tombstones и TTL-expired данные.

Для кластера с разными replication strategies в keyspaces, процент владения данными может быть обманчивым. Указание конкретного keyspace дает точную информацию:

nodetool status my_keyspace

Детальная информация о узле:

nodetool info

Команда возвращает ID узла, датацентр, rack, версию, uptime, heap memory usage, key cache, row cache и другие метрики.

Стратегии уплотнения и их влияние

SSTables накапливаются при каждой записи. Compaction объединяет их, удаляя устаревшие версии и tombstones. Выбор стратегии определяет производительность и использование ресурсов.

SizeTieredCompactionStrategy (STCS) объединяет файлы похожего размера. Когда накапливается четыре SSTable примерно одного размера (контролируется min_threshold), они сливаются в один больший:

CREATE TABLE users (
    user_id uuid PRIMARY KEY,
    name text,
    email text,
    created_at timestamp
) WITH compaction = {
    'class': 'SizeTieredCompactionStrategy',
    'min_threshold': 4,
    'max_threshold': 32,
    'bucket_high': 1.5,
    'bucket_low': 0.5
};

Параметры bucket_high и bucket_low определяют, насколько файлы могут отличаться по размеру, чтобы считаться "похожими". При bucket_high=1.5 файл 100 МБ может объединиться с файлом до 150 МБ.

STCS хорошо работает для равномерной write-heavy нагрузки. Проблема в том, что старые большие файлы редко уплотняются. Таблица на 500 ГБ может содержать удаленные данные годичной давности, занимающие 200 ГБ.

Изменение параметров существующей таблицы:

ALTER TABLE users WITH compaction = {
    'class': 'SizeTieredCompactionStrategy',
    'min_threshold': 6,
    'max_threshold': 32
};

LeveledCompactionStrategy (LCS) организует SSTables в уровни фиксированного размера:

CREATE TABLE events (
    event_id timeuuid PRIMARY KEY,
    user_id uuid,
    event_type text,
    data text
) WITH compaction = {
    'class': 'LeveledCompactionStrategy',
    'sstable_size_in_mb': 160
};

Уровень 0 содержит новые файлы без ограничений. Уровень 1 содержит до 10 файлов по 160 МБ каждый (1.6 ГБ суммарно). Уровень 2 содержит до 100 файлов (16 ГБ). Каждый следующий уровень в 10 раз больше предыдущего.

Ключевое преимущество LCS в том, что каждый ключ существует максимум в одном SSTable на уровень. Запрос проверяет максимум один файл на уровень. Для кластера с 5 уровнями worst-case сценарий читает 5 файлов вместо потенциально сотен при STCS.

Недостаток LCS в высокой нагрузке на диск. Данные многократно переписываются при продвижении между уровнями. Write amplification может достигать коэффициента 10-20. Таблица с 100 ГБ новых данных может генерировать 1-2 ТБ операций записи на диск через compaction.

TimeWindowCompactionStrategy (TWCS) создана для временных рядов:

CREATE TABLE sensor_data (
    sensor_id uuid,
    timestamp timestamp,
    temperature double,
    humidity double,
    PRIMARY KEY (sensor_id, timestamp)
) WITH compaction = {
    'class': 'TimeWindowCompactionStrategy',
    'compaction_window_unit': 'DAYS',
    'compaction_window_size': 1
};

SSTables группируются в временные окна. Данные за 1 января создают свое окно, данные за 2 января создают другое окно. Окна не смешиваются. Внутри окна файлы уплотняются обычным образом.

Для данных с TTL TWCS показывает радикальную эффективность. Если все данные в окне истекли, целый SSTable удаляется без чтения содержимого:

CREATE TABLE metrics (
    metric_name text,
    timestamp timestamp,
    value double,
    PRIMARY KEY (metric_name, timestamp)
) WITH compaction = {
    'class': 'TimeWindowCompactionStrategy',
    'compaction_window_unit': 'HOURS',
    'compaction_window_size': 1
}
AND default_time_to_live = 86400;

Данные живут 24 часа (86400 секунд). Окно создается каждый час. Через 25 часов окно целиком удаляется. Нет tombstones, нет validation, нет overhead.

Параметр gc_grace_seconds критичен для всех стратегий:

ALTER TABLE events WITH gc_grace_seconds = 604800;

Значение 604800 секунд равняется 7 дням. Tombstones сохраняются минимум 7 дней перед удалением во время compaction. Если узел был недоступен 5 дней, вернулся, и repair не запустился в течение следующих 2 дней, tombstones могут удалиться раньше, чем узел получит информацию об удалениях. Результат? Удаленные данные возвращаются.

Инструмент sstableexpiredblockers показывает, какие SSTables блокируют удаление tombstones:

sstableexpiredblockers my_keyspace my_table

Принудительное уплотнение таблицы:

nodetool compact my_keyspace my_table

Для освобождения места от tombstones доступна команда garbagecollect:

nodetool garbagecollect my_keyspace my_table

Эта команда запускает серию небольших compactions, проверяющих перекрывающиеся SSTables. Требует меньше свободного места на диске, чем полное уплотнение, но потребляет больше CPU.

Мониторинг производительности compaction через tablestats:

nodetool tablestats my_keyspace.my_table

Вывод включает метрики: Space used (live), Space used (total), SSTable count, Number of partitions, Average partition size, Maximum partition size, Compacted partition minimum/maximum bytes.

Высокий SSTable count при низком объеме данных указывает на проблемы с compaction. Таблица на 10 ГБ с 500 SSTables работает медленнее, чем таблица на 100 ГБ с 20 SSTables.

Детальная статистика чтения и записи:

nodetool tablehistograms my_keyspace my_table

Команда показывает распределение latency для операций чтения и записи за последние 15 минут, размеры партиций и количество SSTables, читаемых за запрос.

Переключение между стратегиями требует полного уплотнения существующих данных. Переход с STCS на LCS:

ALTER TABLE users WITH compaction = {
    'class': 'LeveledCompactionStrategy',
    'sstable_size_in_mb': 160
};

Затем принудительное уплотнение:

nodetool compact my_keyspace users

Процесс может занять часы на больших таблицах. На таблице 500 ГБ с STCS может быть 50 больших SSTables. Переход на LCS создаст несколько тысяч файлов по 160 МБ, организованных в уровни. Операция генерирует высокую нагрузку на CPU и диск.

Практические сценарии применения

Сценарий 1: Узел был недоступен 6 часов. Hints истекли через 3 часа. Запуск repair на вернувшемся узле:

nodetool repair -pr

Сценарий 2: Кластер использует QUORUM для записей, но один узел часто недоступен. Tombstones могут не распространиться. Регулярный repair каждые 7 дней:

0 2 * * 0 /usr/bin/nodetool repair -pr >> /var/log/cassandra/repair.log 2>&1

Cron запускает repair каждое воскресенье в 2 часа ночи.

Сценарий 3: Таблица временных рядов хранит данные с TTL 30 дней. STCS оставляет истекшие данные на диске. Переход на TWCS с суточными окнами:

ALTER TABLE metrics WITH compaction = {
    'class': 'TimeWindowCompactionStrategy',
    'compaction_window_unit': 'DAYS',
    'compaction_window_size': 1
};

Через 30 дней старые окна удаляются целиком, освобождая пространство без overhead.

Выбор стратегии зависит от паттерна доступа. Равномерное распределение операций по всем ключам, преимущественно записи, редкие удаления - STCS оптимален. Частые чтения по ключу, требование предсказуемой latency - LCS необходим. Временные данные с удалением старых записей - TWCS идеален.

Ошибка в выборе стратегии приводит к избыточному потреблению дискового пространства и деградации производительности. Понимание внутренних механизмов repair, token ranges и compaction превращает Cassandra из черного ящика в управляемый инструмент.