Это послеотпускное продолжение цикла, рассказывающего о практике развёртывания небольшого, но вполне производственного кластера Cassandra. В первой и второй частях мы продвинулись вперед вот по такому плану:
Анализ рабочей нагрузки и требований
Разработка схемы данных
Настройка хостовых машин
Настройка конфигурации Cassandra
= ВЫ НАХОДИТЕСЬ ЗДЕСЬ =Настройка топологии кластера
Подключение Prometheus Cassandra Exporter
Подключение Prometheus Node Exporter
Вывод всех метрик в Grafana
Проведение нагрузочного тестирования
Дополнительный тюнинг по результатам теста
Двинемся дальше?
5. Настройка топологии кластера
Давайте вспомним, что Cassandra — это распределенная отказоустойчивая БД с репликацией, для которой топологическая конфигурация в большинстве случаев критична. Она влияет на распределение токенов по нодам, отказоустойчивость, объем трафика между нодами и/или датацентрами, балансировку нод и общую производительность вкупе с задержками.
Механизм vnodes
в современных версиях Cassandra частично нивелирует возможные проблемы неравномерного распределения данных, однако для критичных систем лучше явно управлять топологией.
Пожалуй, настройкой топологии можно пренебречь, только если вы разворачиваете простейший кластер из одной ноды, используете один физический сервер или поднимаете тестовый/учебный стенд. Во всех остальных случаях, — и тем более для производственной среды! — топологической конфигурации кластера однозначно следует уделить внимание.
Уровень согласованности и фактор репликации
Первым ключевым параметром, отвечающим за репликацию данных, является Consistency Level (уровень согласованности). Он определяет, сколько реплик данных должны подтвердить выполнение операции чтения или записи, чтобы последняя считалась успешной.
Выбор параметра позволяет балансировать между скоростью, согласованностью данных и отказоустойчивостью в распределённой системе. Consistency Level указывается для каждой операции чтения или записи и непосредственным образом связан с топологией кластера.
Данные в Cassandra реплицируются на несколько нод согласно Replication Factor, RF (фактором репликации), который указывается на уровне keyspace. Например, при RF=3 каждая запись хранится на трёх нодах.
Интересно, что Replication Factor можно поменять в любой момент и Cassandra “на лету” изменит количество реплик в кластере для этого keyspace (может потребоваться nodetool repair
). Это не очень частый кейс, однако иногда необходимый. Подытоживая можно сказать, что для большинства сценариев использования RF=3 — самый частый вариант.
Consistency Level может принимать следующие значения:
ONE: Требует подтверждение от одной реплики. Самый быстрый, но наименее согласованный. Возможна утрата консистентности данных;
TWO / THREE: Требуется подтверждение от двух или трёх реплик соответственно;
QUORUM: Требуется подтверждение от большинства реплик. Рассчитывается как округленное вверх RF/2 (т.е. для RF=3 кворум составляет 2 реплики);
ALL: Требуется подтверждение от всех реплик. Максимальная согласованность, но минимальная отказоустойчивость — отказ даже одной ноды будет означать, что операция провалилась;
LOCAL_QUORUM: Кворум в пределах одного датацентра (для мульти-ДЦ кластеров);
LOCAL_ONE: Подтверждение от одной реплики в локальном датацентре;
ANY (только для записи): Запись считается успешной, даже если она дошла только до координатора транзакций.
Интуитивно понятно, что распределение реплик по разным ДЦ (или хотя бы физическим стойкам), с одной стороны, увеличивает надёжность, но с другой — увеличивает и задержки из-за передачи данных в удаленный ДЦ. Хотя, если датацентр с одной из реплик сгорит, а ваш кластер продолжит работать как ни в чём ни бывало, то руководство вам выдаст премию скажет спасибо за предусмотрительность.
Исправления в конфигах
У нас будут три поинта изменений в конфигурационных файлах деплоя, плюс один временный патч, требующийся при исправлении топологии (пример ниже).
Поинт раз. В основном конфиге Cassandra исправим параметр endpoint_snitch
, который может принимать следующие значения:
SimpleSnitch
— дефолтный, не учитывает топологиюGossipingPropertyFileSnitch
— наш случайRackInferringSnitch
— для простого локального развертыванияEc2Snitch
— для AWS
# deploy/templates/cassandra.j2
endpoint_snitch: GossipingPropertyFileSnitch
Поинт два. Теперь поместим 5 файлов cassandra1.yml
… cassandra5.yml
(по числу нод) в папку deploy/host_vars
с Ansible-переменными внутри:
# deploy/host_vars/cassandra1.yml
cassandra_datacenter: SBG2
cassandra_rack: rack1
---
# deploy/host_vars/cassandra2.yml
cassandra_datacenter: SBG2
cassandra_rack: rack2
---
# deploy/host_vars/cassandra3.yml
cassandra_datacenter: SBG2
cassandra_rack: rack1
---
# deploy/host_vars/cassandra4.yml
cassandra_datacenter: SBG2
cassandra_rack: rack2
---
# deploy/host_vars/cassandra5.yml
cassandra_datacenter: SBG2
cassandra_rack: rack2
Лучше не шутить и указать реальное наименование ДЦ, чтобы избежать путаницы и облегчить техническое сопровождение кластера в будущем.
Поинт три. Добавим этот Ansible-шаблон:
# deploy/templates/cassandra-rackdc.properties.j2
dc={{ cassandra_datacenter }}
rack={{ cassandra_rack }}
Который просто подставит переменные из соответствующих значений, находящихся в deploy/host_vars/cassandra*.yml
. А после развёртывания станет файлом /etc/cassandra/cassandra-rackdc.properties
на каждой ноде. Именно последний и определяет физическое место ноды в кластере через пару параметров “датацентр + стойка”.
Рисуем топологию
Идеально, если выбор конкретных ДЦ и стоек получится сделать на самом начальном этапе после проектирования архитектуры, когда кластера еще нет, как такового.
Хорошо, если у вас есть точная инфа о физических стойках, в которых размещены сервера. Но что делать, если провайдер не предоставляет такие данные? В этом случае есть смысл, во-первых, отталкиваться от IP-адресов хост-машин (разные подсети, вероятнее всего, будут находиться в разных стойках), а, во-вторых, использовать синтетическую топологию, которая, как ни удивительно, окажется лучше, чем если ее не будет вообще.
На полях замечу, что для тяжелых распределенных кластеров из десятков и сотен нод придётся учитывать намного больше параметров, включая гео расположение ДЦ, его близость к потребителю нагрузки, пропускную способность и резервирования канала между разными датацентрами и много других факторов, которые мы здесь набрасывать на вентилятор рассматривать не будем.
Итак, перед проектированием топологии нашего кластера потребуется:
Определиться с Consistency Level
Определить количество физических нод (5 в нашем случае)
Определить Replication Factor (3 в нашем случае)
Выбрать хост-машины в затребованных ДЦ и стойках (идеальный сценарий)
Или, если хост-машины находятся в одном ДЦ, а провайдер не предоставил инфы о стойках, тогда:Сделать допущения о расположении хостов исходя из их IP-адресов
Давайте попробуем сообразить топологию для последнего случая, когда хост-машины размещены в одном ДЦ, а инфа о размещении серверов в стойках отсутствует. На этом примере вы легко поймете что делать в более благоприятном случае, когда ДЦ и стойки можно выбрать и заказать, как требуется.
Допустим, у нас есть пять хост-машин для будущих нод со следующими IP в “разных подсетях”, которым мы сразу попробуем присвоить условную физическую стойку в датацентре:
192.168.0.1 → rack1
192.168.0.2 → rack1
192.168.32.3 → rack2
192.168.32.4 → rack2
192.168.64.0 → rack3
Собственно говоря, это и есть синтетическая топология, сделанная с известной долей допущения. Ее плюс в том, что при RF=3 Cassanda успешно раскидает реплики данных вот таким образом:
Три реплики сюда: 192.168.0.1, 192.168.32.3, 192.168.64.0
И три реплики сюда: 192.168.0.2, 192.168.32.4, 192.168.64.0
Теперь, если одна любая стойка упадет, то у нас все равно останутся 2 копии данных и кворум. То есть кластер продолжит работать. Звучит неплохо, согласны?
Однако, если наше допущение о стойках/подсетях оказалось неверным, и хост-машины физически размещены иначе, то вероятность, что они окажутся в разных стойках (пусть и в другой комбинации) хотя и не гарантирована, но достаточно высока. А значит реплики данных всё равно с большой вероятностью распределятся по разным стойкам, что и требуется для отказоустойчивости.
Окей, а в чём подвох?
Подвох я осознал уже после запуска кластера и вывода команды nodetool status
в консоли, которая мне логично (ну теперь-то задним числом это логично!) показала в колонке “Owns” распределение токенов по нодам: по 66% на каждую из первых 4-х нод и 100% на 5-ю ноду (192.168.64.0 → rack3).
А теперь давайте ответим на пару вопросов “Почему?” и “Ну и что?” :)
Потому, что Cassandra использует предложенную топологию для оптимальной репликации и в этой конкретной конфигурации кластера совершенно справедливо будет стараться (и скорее всего успешно) разместить одну реплику данных на rack3. Так хорошо же, а что не так?
А то что 5-я нода будет хранить 100% данных кластера. И сервер ноды будет работать с максимальной нагрузкой. В то время как остальные ноды получат нагрузку не более чем условные 66% от всегда перегруженной пятой.
Иными словами, единственная нода в rack3 становится обязательной для размещения одной из трех реплик практически всех данных, что приведет к разбалансировке кластера, в котором один сервер эксплуатируется на пределе. И рано или поздно наступят последствия.
Вторая попытка: исправляем топологию
Дополнительный шестой сервер для идеально сбалансированного кластера мне не дали я просить не стал, а просто “повесил” 5-ю ноду на rack2. Получилось вот так:
192.168.0.1 → rack1
192.168.0.2 → rack1
192.168.32.3 → rack2
192.168.32.4 → rack2
192.168.64.0 → rack2
С таким конфигом кластер уже становится вполне сбалансированным.
Cassandra поддерживает “горячее” изменение топологии кластера, поскольку добавление и удаление нод хотя и не слишком частая, но вполне рутинная процедура для распределенной высоконагруженной системы. Примеры — ремонт и обслуживание, переезд нод в другую стойку или ДЦ, масштабирование кластера (в любую сторону).
Вот как это делается на работающем кластере под нагрузкой:
1) Остановите целевую ноду;
2) Сделайте специфический временный патч в конфиге, чтобы Cassandra приняла изменение топологии кластера:
# docker-compose.yaml
services:
cassandra:
environment:
- JVM_OPTS=-Dcassandra.ignore_dc=true -Dcassandra.ignore_rack=true
3) Сразу после успешного подключения к кластеру сделайте nodetool repair
для пере-синхронизации кластера и дождитесь завершения операции;
4) Снова остановите ноду, удалите временный патч и перезапустите в нормальном режиме.
Заметьте, что в этот раз мы исправляем НЕ deploy/templates/docker-compose.j2
и, соответственно, НЕ раскатываем патч на весь кластер, а меняем только docker-compose.yaml
непосредственно на целевой ноде. И на ней же делаются все остальные операции.
Не оставляйте этот патч в конфиге после успешного изменения топологии! Это чревато конфликтами, нарушениям consistency и replication, ошибками основных операций nodetool и другими ужасами последствиями.
Важные нюансы для продакшена
Время выполнения: Операция
nodetool repair
может занимать часы или даже дни на больших кластерах. Планируйте изменения топологии в период минимальной нагрузки;Мониторинг ресурсов: Во время repair нагрузка на хост-машину существенно возрастает — проверьте, что у серверов есть запас производительности;
Поэтапность: Меняйте топологию по одной ноде за раз, дожидаясь полного завершения repair перед переходом к следующей ноде;
Бэкапы: Делайте полный снапшот кластера перед изменениями топологии.
Контроль балансировки
После любых изменений топологии проверьте распределение данных:
# Общая инфа и проверка распределения
nodetool status
# Детальная информация о кольце токенов
nodetool ring
# Проверка эффективности распределения данных
nodetool cfstats keyspace_name.table_name
Обращайте внимание на колонку "Owns" в выводе nodetool status
. В хорошо сбалансированном кластере каждая из нод должна владеть примерно одинаковым объемом данных. Если разброс существенный, то стоит пересмотреть топологию или провести nodetool cleanup
после изменения конфигурации.
Подводим итог
Универсальное правило: Количество нод в кластере должно быть кратно RF, а количество стоек - делителем RF. Идеальные комбинации: 3 ноды в 3 стойках, 6 нод в 2-3 стойках, 9 нод в 3 стойках и т.д.
Теперь у вас сложилась некоторая картина об особенностях топологической конфигурации Cassandra, и наверняка появились дельные мысли о необходимости выбора правильного количества серверов/нод и их будущего размещения еще на этапе проектирования архитектуры кластера. Конечно, с учетом предполагаемых Consistency Level и Replication Factor и других параметров.
Продолжение следует.