Реляционные базы данных правят миром уже несколько десятилетий, и для большинства задач их хватает с избытком. Но иногда приходит проект, в котором SQL начинает скрипеть и трещать под нагрузкой. Миллиарды записей, тысячи операций записи в секунду, географически распределённая инфраструктура с дата-центрами на разных континентах - в таких сценариях классическая база с её строгими таблицами и единым мастером превращается в узкое горлышко. И тут на сцену выходят NoSQL-решения, среди которых Apache Cassandra занимает одно из первых мест.
Cassandra появилась в середине двухтысячных как внутренний проект одной крупной американской компании, потом была отдана сообществу через Apache Software Foundation, и за это время превратилась в один из самых проверенных временем инструментов для работы с большими объёмами данных. Это распределённая база, которая умеет горизонтально масштабироваться - стоит добавить новый узел, и нагрузка автоматически перераспределится. Узлы можно добавлять и удалять без остановки работы кластера, а данные между ними реплицируются прозрачно. Логически кластер организуется как кольцо, где каждый узел отвечает за свой диапазон ключей и одновременно хранит копии данных соседей.
Среди пользователей Cassandra такие тяжеловесы, как Netflix, Apple, Reddit, Uber и сотни других компаний с серьёзными нагрузками. Но прежде чем разворачивать многоузловой кластер на десятки серверов, имеет смысл познакомиться с продуктом на одиночном узле. Ниже разобран полный путь установки на Ubuntu 22.04 - от подготовки Java-окружения до базовой настройки безопасности и переименования кластера.
Подготовительные требования и базовое окружение для развёртывания распределённой базы
Чтобы пройти этот путь без сюрпризов, понадобится сервер на Ubuntu 22.04 с минимумом 2 ГБ оперативной памяти. Это абсолютный минимум для одноузловой установки в учебных целях. Для боевой эксплуатации цифра должна быть в разы больше - Cassandra любит память, и под серьёзной нагрузкой каждый узел спокойно съедает 16-32 ГБ только под кучу JVM. Дисковое пространство тоже стоит закладывать с запасом, желательно на SSD с быстрыми операциями записи.
Доступ к серверу должен быть из-под обычного пользователя с правами sudo. Постоянная работа из-под root считается дурным тоном с давних времён, и для сетевых сервисов это особенно критично - любая компрометация процесса базы данных моментально превращается в полный контроль над системой.
Обновляем пакеты до последних версий перед началом:
$ sudo apt update && sudo apt upgrade
Команда сначала обновляет индекс репозиториев, а затем устанавливает все доступные обновления. Привычка делать update перед серьёзными работами экономит часы будущих разбирательств с устаревшими версиями.
Установка OpenJDK 11 как основы для работы виртуальной машины Java
Cassandra написана на Java и не запустится без подходящей JVM в системе. Поддерживаются две версии - Java 8 и Java 11. Восьмая давно считается устаревшей и используется в основном для совместимости с легаси-системами. Современные релизы Cassandra полностью поддерживают одиннадцатую версию, и именно её разумно ставить на новый сервер.
Ставим OpenJDK 11:
$ sudo apt install openjdk-11-jdk
Пакет openjdk-11-jdk включает не только саму JVM для запуска приложений, но и инструменты разработчика - компилятор javac, утилиту работы с архивами jar и многое другое. Для запуска самой Cassandra хватило бы и openjdk-11-jre без -jdk суффикса, но имея под рукой полный набор, проще диагностировать проблемы и работать с расширениями.
Проверяем результат:
$ java -version
Ожидаемый вывод:
openjdk 11.0.15 2022-04-19
OpenJDK Runtime Environment (build 11.0.15+10-Ubuntu-0ubuntu0.22.04.1)
OpenJDK 64-Bit Server VM (build 11.0.15+10-Ubuntu-0ubuntu0.22.04.1, mixed mode, sharing)
Цифры конкретной сборки будут отличаться - зависит от того, когда обновлялся пакет в репозитории. Главное - что Java определилась, версия 11, и режим работы 64-Bit Server VM. Серверный режим JVM оптимизирован для длительно работающих приложений с упором на пиковую производительность, в отличие от клиентского, который оптимизирует время старта.
Подключение официального репозитория Apache и установка пакета Cassandra
В стандартном репозитории Ubuntu Cassandra присутствует, но обычно там лежит относительно старая версия. Проект развивается активно, и каждая новая ветка приносит исправления багов, оптимизации производительности и новые возможности. Поэтому правильнее ставить через официальный репозиторий Apache.
Добавляем источник в список репозиториев:
$ echo "deb http://www.apache.org/dist/cassandra/debian 40x main" | sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list
В этой команде стоит обратить внимание на сегмент 40x в URL. Это обозначение серии 4.0 - именно из неё будут устанавливаться пакеты. Для других версий нужно подставить соответствующее значение - 41x для 4.1, 50x для 5.0 и так далее. На момент написания оригинала актуальной была версия 4.0.5.
Добавляем подписной ключ репозитория:
$ wget -q -O - https://www.apache.org/dist/cassandra/KEYS | sudo tee /etc/apt/trusted.gpg.d/cassandra.asc
Команда скачивает файл с ключами Apache, который содержит подписи всех мейнтейнеров Cassandra, и сохраняет его в стандартный каталог доверенных ключей apt. Без этого шага apt будет ругаться на пакеты неподписанным репозиторием и откажется их ставить.
Обновляем индекс пакетов:
$ sudo apt update
Ставим Cassandra:
$ sudo apt install cassandra
Менеджер пакетов выкачает дистрибутив со всеми зависимостями, развернёт файлы по нужным местам файловой системы, создаст пользователя cassandra для работы службы и автоматически запустит её.
Проверка корректной работы службы и осмотр кольца кластера через nodetool
После установки Cassandra стартует автоматически. Проверяем статус через systemd:
$ sudo systemctl status cassandra
Ожидаемый вывод:
? cassandra.service - LSB: distributed storage system for structured data
Loaded: loaded (/etc/init.d/cassandra; generated)
Active: active (running) since Mon 2022-07-25 11:40:42 UTC; 12min ago
Docs: man:systemd-sysv-generator(8)
Tasks: 48 (limit: 2241)
Memory: 1.3G
CPU: 38.219s
CGroup: /system.slice/cassandra.service
??4602 /usr/bin/java -ea -da:net.openhft... -XX:+UseThreadPriorities -XX:+HeapDumpOnOutOfMemoryError -Xss256k -XX:+AlwaysPreTouch -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+UseNUMA ->
Jul 25 11:40:42 cassandra systemd[1]: Starting LSB: distributed storage system for structured data...
Jul 25 11:40:42 cassandra systemd[1]: Started LSB: distributed storage system for structured data.
Любопытная деталь - служба загружается из /etc/init.d/cassandra, что говорит о LSB-стиле скрипта инициализации, обёрнутого systemd-генератором. Это историческое наследие пакетной сборки, на функциональности никак не сказывается. Главное - в выводе видно active (running), означающее работоспособность службы.
В дереве процессов видна красивая длинная строка JVM с десятками параметров - управление потоками, дамп при OutOfMemory, настройки выделения памяти, поддержка NUMA-архитектур. Эти параметры собраны разработчиками Cassandra на основе многолетнего опыта эксплуатации и обычно их не нужно трогать без серьёзных причин.
Память службы 1.3G даже на простом старте - наглядный пример, почему Cassandra считается прожорливой. На реальной нагрузке этот показатель будет в разы больше.
Дополнительная проверка через специальную утилиту для управления кластером:
$ nodetool status
Ожидаемый вывод:
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 127.0.0.1 69.08 KiB 16 100.0% 6690243a-950d-4d64-9463-aa722f8064d4 rack1
Здесь видна вся структура кластера. Имя дата-центра datacenter1 - значение по умолчанию, в продакшене обычно меняется на что-то осмысленное (eu-west, us-east, dc-msk и так далее). Под заголовком Status=Up/Down и State=Normal видна одна строка - наш единственный узел.
Префикс UN расшифровывается как Up Normal - узел поднят и работает в нормальном режиме. Адрес 127.0.0.1 показывает, что Cassandra пока слушает только локальный интерфейс. Load - объём данных на узле, пока всего 69 кибибайт. Tokens 16 - количество токенов, по которым этот узел отвечает в кольце. Owns (effective) показывает процент данных, за которые отвечает этот узел - в одноузловом кластере это, естественно, 100%. Host ID - уникальный идентификатор узла, под которым его знают другие участники кольца. Rack rack1 - имя стойки, ещё одна группировка для физической топологии.
Всё это устройство имеет смысл при работе с настоящим кластером из десятков узлов в нескольких дата-центрах. Cassandra умеет учитывать расположение узлов по стойкам и центрам, чтобы реплики данных хранились в разных физических местах для защиты от сбоев питания или сетевых проблем.
Включение пользовательской аутентификации через правку основного конфигурационного файла
Дефолтная установка Cassandra не требует никакой аутентификации - любой, кто знает адрес и порт, может подключиться и делать что угодно. Для тестов это удобно, для боевого использования совершенно недопустимо. Включаем парольную защиту.
Конфигурационные файлы Cassandra живут в /etc/cassandra. Логи пишутся в /var/log/cassandra, данные хранятся в /var/lib/cassandra. Это стандартная структура для Linux-дистрибутивов, удобная для бэкапов и аудита. Параметры JVM (включая размер кучи) задаются через файл /etc/cassandra/cassandra-env.sh - туда же можно прокидывать дополнительные аргументы JVM через переменную JVM_OPTS.
Сначала делаем резервную копию основного конфига перед правкой:
$ sudo cp /etc/cassandra/cassandra.yaml /etc/cassandra/cassandra.yaml.backup
Привычка сохранять оригинал перед серьёзными изменениями спасала уже не одно поколение администраторов. Когда что-то идёт не так, всегда можно сравнить через diff и понять, какая именно правка сломала систему.
Открываем файл:
$ sudo nano /etc/cassandra/cassandra.yaml
Находим четыре параметра по всему файлу:
...
authenticator: AllowAllAuthenticator
...
authorizer: AllowAllAuthorizer
...
roles_validity_in_ms: 2000
...
permissions_validity_in_ms: 2000
...
Меняем их значения на следующие:
...
authenticator: org.apache.cassandra.auth.PasswordAuthenticator
...
authorizer: org.apache.cassandra.auth.CassandraAuthorizer
...
roles_validity_in_ms: 0
...
permissions_validity_in_ms: 0
. . .
Разберём по порядку. Параметр authenticator определяет, как именно проверяются учётные данные при подключении. Значение AllowAllAuthenticator буквально означает "пускай всех" и используется только в незащищённой конфигурации. Замена на org.apache.cassandra.auth.PasswordAuthenticator включает проверку логина и пароля.
Параметр authorizer отвечает за проверку прав на конкретные действия. AllowAllAuthorizer разрешает всё всем. Замена на org.apache.cassandra.auth.CassandraAuthorizer включает гранулярную систему прав, в которой можно выдавать конкретным ролям конкретные привилегии на конкретные таблицы.
Параметры roles_validity_in_ms и permissions_validity_in_ms задают время кеширования информации о ролях и правах. Дефолтное значение 2000 миллисекунд означает, что изменения в правах применяются с задержкой до двух секунд. Установка в 0 отключает кеш совсем - правки вступают в силу немедленно, что удобно при настройке. В боевом окружении кеш обычно возвращают, поскольку он заметно снижает нагрузку при большом числе подключений.
Сохраняем файл и перезапускаем Cassandra:
$ sudo systemctl restart cassandra
После рестарта новые правила вступят в силу. Любая попытка подключиться без учётных данных теперь будет отклоняться.
Создание административного суперпользователя и отзыв прав у дефолтной учётной записи
После включения аутентификации в системе остаётся дефолтный пользователь cassandra с одноимённым паролем. Это огромная дыра - такая комбинация известна каждому, кто хоть раз сталкивался с продуктом, и сканеры её перебирают в первую очередь. Поэтому первым делом создаётся настоящий администратор, а у дефолтной учётки отбираются все права.
Подключаемся к консоли через cqlsh с дефолтными учётными данными:
$ cqlsh -u cassandra -p cassandra
Появится приглашение оболочки:
Connected to Test Cluster at 127.0.0.1:9042
[cqlsh 6.0.0 | Cassandra 4.0.5 | CQL spec 3.4.5 | Native protocol v5]
Use HELP for help.
cassandra@cqlsh>
Видны полезные подсказки - имя кластера (Test Cluster, дефолтное), адрес и порт подключения, версия cqlsh, версия Cassandra, версия диалекта CQL и протокола. CQL - это Cassandra Query Language, синтаксически похожий на SQL, но с отличиями, продиктованными распределённой природой базы.
Создаём нового суперпользователя:
cassandra@cqlsh> CREATE ROLE [username] WITH PASSWORD = '[yourpassword]' AND SUPERUSER = true AND LOGIN = true;
Заменяем [username] и [yourpassword] на реальные значения. Команда CREATE ROLE создаёт сущность, которая может быть как пользователем, так и группой ролей. Параметр SUPERUSER = true даёт полные административные права. LOGIN = true разрешает использовать роль для подключения (без этого флага роль может быть только контейнером прав, но не учётной записью для входа).
Выходим из оболочки:
cassandra@cqlsh> exit
Заходим обратно с новым суперпользователем:
$ cqlsh -u username -p yourpassword
Теперь главный момент - снимаем привилегии с дефолтной учётки cassandra:
username@cqlsh> ALTER ROLE cassandra WITH PASSWORD = 'cassandra' AND SUPERUSER = false AND LOGIN = false;
username@cqlsh> REVOKE ALL PERMISSIONS ON ALL KEYSPACES FROM cassandra;
Первая команда сбрасывает суперпользовательский флаг и запрещает вход под этой ролью. Пароль для неё оставляется тот же (cassandra), но это уже неважно - LOGIN = false делает любые попытки подключения безуспешными. Вторая команда отзывает все разрешения у этой роли на все keyspace - чтобы даже теоретически через какую-то дыру через эту роль ничего нельзя было сделать.
Дополнительно явно выдаём все права новому суперпользователю:
username@cqlsh> GRANT ALL PERMISSIONS ON ALL KEYSPACES TO '[username]';
Хотя SUPERUSER = true уже даёт все права, явное GRANT - это страховка. На случай, если кто-то когда-то решит снять флаг суперпользователя, права на keyspace останутся при роли.
Выходим:
username@cqlsh> exit
Теперь система защищена нормально. Дефолтная пара cassandra/cassandra больше не работает, есть личная административная учётка с собственным паролем.
Настройка файла cqlshrc для удобной работы с консольной оболочкой Cassandra
Каждый раз вводить логин и пароль при запуске cqlsh - утомительно. Для удобства работы можно настроить файл конфигурации оболочки, в котором сохранятся учётные данные и другие предпочтения. Файл называется cqlshrc и по умолчанию ищется в каталоге ~/.cassandra. Если запускать cqlsh с параметром --cqlshrc /custompath, можно указать другое расположение.
В дефолтной установке файла нет - его нужно создать. Все операции в этом каталоге делаются от имени локального пользователя без sudo, поскольку Cassandra требует, чтобы файлы здесь принадлежали именно текущей учётной записи.
Создаём файл:
$ touch ~/.cassandra/cqlshrc
Если каталога ~/.cassandra ещё нет, создаём его:
$ mkdir ~/.cassandra
Открываем файл для правки:
$ nano ~/.cassandra/cqlshrc
В репозитории Cassandra на GitHub есть пример полного cqlshrc - там видны все доступные секции с комментариями. Нас интересует секция аутентификации:
....
[authentication]
;; If Cassandra has auth enabled, fill out these options
username = [username]
password = [password]
....
Подставляем сюда логин и пароль администратора, созданного на предыдущем шаге. Все настройки в примере закомментированы через двойной апостроф ;; - такой стиль комментариев в формате конфигов оболочки. Чтобы строка работала, надо убрать эти комментарии.
Сохраняем файл и устанавливаем правильные права:
$ chmod 600 ~/.cassandra/cqlshrc
Права 600 означают чтение и запись только для владельца, для всех остальных доступ закрыт. Это критично - в файле лежит пароль в открытом виде, и никто посторонний не должен его видеть. Без правильных прав другие пользователи системы могли бы прочитать содержимое.
Проверяем результат:
$ cqlsh
Теперь команда запускается без параметров и сразу подключается под нужным пользователем:
Connected to Test Cluster at 127.0.0.1:9042
[cqlsh 6.0.0 | Cassandra 4.0.5 | CQL spec 3.4.5 | Native protocol v5]
Use HELP for help.
navjot@cqlsh>
Стоит держать в голове важное замечание - метод хранения логина и пароля в cqlshrc объявлен устаревшим начиная с Cassandra 4.1. В новых версиях рекомендуется использовать другие механизмы вроде интеграции с системами управления секретами (HashiCorp Vault, AWS Secrets Manager) или передачу учётных данных через переменные окружения. Для одноузловых установок и тестовых сред старый способ всё ещё работает, но для боевой эксплуатации стоит изучить современные альтернативы.
Переименование кластера из стандартного Test Cluster в собственное осмысленное название
Кластер по умолчанию носит звучное имя Test Cluster - оно фигурирует везде в логах и приветственных строках. Для серьёзной работы хочется чего-то более осмысленного, отражающего назначение системы. Например, prod-cassandra, analytics-ring или users-data.
Переименование - двухэтапный процесс. Сначала меняется имя в системной таблице базы, потом в конфигурационном файле. Без обоих шагов изменение не закрепится.
Заходим в оболочку:
$ cqlsh
Обновляем системную запись (заменяя [clustername] на желаемое имя):
username@cqlsh> UPDATE system.local SET cluster_name = '[new_name]' WHERE KEY = 'local';
Системная таблица system.local хранит метаинформацию о текущем узле - его положение в кластере, токены, идентификаторы. Здесь же хранится и имя кластера.
Выходим из оболочки:
username@cqlsh> exit
Открываем конфигурационный файл для второго шага:
$ sudo nano /etc/cassandra/cassandra.yaml
Находим параметр cluster_name и подставляем то же имя:
...
# The name of the cluster. This is mainly used to prevent machines in
# one logical cluster from joining another.
cluster_name: '[new_name]'
...
Комментарий в файле подсказывает, для чего нужно имя кластера - для защиты от случайного присоединения узла к чужому кольцу. Если на одном сервере поднимается тестовый узел с таким же именем, как у боевого кластера, теоретически возможна катастрофа с реплицированием данных не туда. Поэтому имена должны быть уникальны и осмысленны.
Сохраняем файл.
Сбрасываем системный кеш Cassandra:
$ nodetool flush system
Эта команда принудительно записывает закешированные изменения системного keyspace на диск. Без неё переименование могло бы потеряться при следующем старте, если flush ещё не произошёл.
Перезапускаем службу:
$ sudo systemctl restart cassandra
Проверяем результат через подключение к оболочке:
$ cqlsh
Теперь приветствие выглядит иначе:
Connected to howtoforge at 127.0.0.1:9042
[cqlsh 6.0.0 | Cassandra 4.0.5 | CQL spec 3.4.5 | Native protocol v5]
Use HELP for help.
navjot@cqlsh>
Имя кластера обновилось, и все будущие операции будут происходить уже в кластере с новым именем.
Распространённые подводные камни и практические наблюдения из реальной эксплуатации Cassandra
В копилку наблюдений из практики стоит добавить несколько моментов, на которых обжигаются те, кто впервые поднимает Cassandra в боевом окружении.
Первая частая проблема - выделение памяти JVM. Cassandra по умолчанию пытается выделить себе половину доступной оперативной памяти под кучу. На сервере с 32 ГБ это даст 16 ГБ под heap, что выше рекомендованного максимума в 8 ГБ для большинства сценариев. Слишком большая куча приводит к долгим паузам сборки мусора, которые видны клиентам как замирания базы. Лекарство - явно настраивать MAX_HEAP_SIZE и HEAP_NEWSIZE в /etc/cassandra/cassandra-env.sh. Разумная отправная точка для большинства узлов - 8 ГБ под heap независимо от общего объёма памяти.
Вторая популярная грабля - размещение коммит-логов и данных на одном диске. Cassandra записывает коммит-лог последовательно, а данные SSTable распределяет случайно по всему диску. Когда оба процесса делят один SSD, оба замедляют друг друга. Идеальная схема - коммит-лог на отдельном (желательно быстром NVMe) диске, данные на основном хранилище. Каталоги настраиваются через параметры commitlog_directory и data_file_directories в cassandra.yaml.
Третий момент касается репликации. Дефолтный SimpleStrategy для размещения реплик не учитывает географию. На одноузловой установке это неважно, но как только появляются второй, третий, десятый узел, нужно переходить на NetworkTopologyStrategy и явно описывать дата-центры и стойки в snitch-конфигурации. Без этого все реплики могут оказаться в одном дата-центре, и его падение оставит кластер без данных.
Четвёртая тонкость - регулярный repair. Cassandra обеспечивает eventual consistency, и со временем реплики могут расходиться из-за временных проблем с сетью или сбоев узлов. Команду nodetool repair нужно запускать регулярно (раз в неделю как минимум), иначе расхождения накапливаются и в один прекрасный момент чтение начинает возвращать неожиданные результаты. Многие команды используют для этого специальные инструменты вроде Reaper, которые автоматизируют процесс репараций.
Пятый момент - мониторинг. Cassandra экспортирует огромное количество метрик через JMX, и без их сбора эксплуатация кластера превращается в работу вслепую. Настраивать сбор через Prometheus с jmx-exporter или через специализированные системы вроде DataStax OpsCenter нужно сразу, пока проект не разросся до критичного размера. Без метрик заметить надвигающуюся проблему практически невозможно.
Где такая инсталляция пригодится в реальной жизни? Сценариев масса. Хранение временных рядов с миллиардами записей - метрики IoT-устройств, логи серверов, телеметрия игр. Системы рекомендаций с быстрым доступом по ключу пользователя. Каталоги товаров для крупных интернет-магазинов с распределением по регионам. Учётные системы биллинга, где скорость записи критичнее сложных транзакций. Аналитические витрины поверх HDFS-кластеров. Хранение чатов и сообщений в мессенджерах с миллионами активных пользователей.
Освоение Cassandra даёт инженеру не просто навык работы с одной базой данных, а целое погружение в мир распределённых систем. Понимание принципов eventual consistency, концепции CAP-теоремы на практике, работа с кольцами и токенами, настройка репликации между дата-центрами - всё это переносимо на десятки других продуктов в смежных областях. Тот, кто разобрался с Cassandra, без труда осваивает ScyllaDB (по сути её наследницу с акцентом на производительность), Amazon DynamoDB, Apache HBase, MongoDB и многие другие современные хранилища. И именно поэтому даже простая одноузловая установка стоит того, чтобы её сделать своими руками - это первый шаг в большой мир архитектур, где данные перестают помещаться на одном сервере и приходится мыслить категориями кластеров, регионов и устойчивости к отказам.