Быть или не быть? Стоит ли использовать key-value-базы данных в большом продакшне? На связи Иван Храмов, CTO МТС ID, и Николай Диденко, техлид из команды инфраструктуры МТС Web Services. Мы используем Cassandra в МТС ID и за годы эксплуатации познали и сильные, и слабые стороны этого решения.

Главная особенность и одновременно ограничение Cassandra и ScyllaDb — это то, что они строго key-value-хранилища. Именно с этим они справляются отлично — быстрое чтение и запись по ключу, георезервирование и масштабирование. На этом этапе все выглядит радужно.

Но по мере роста проекта возникает необходимость более сложной работы с данными. Например, когда хочется получить информацию в разрезе дат или понять, на каких устройствах какие токены живут. И вот здесь начинают всплывать ограничения архитектуры и типовые грабли, на которые можно наступить (и мы регулярно это делали). В этом материале мы опишем, почему выбрали Cassandra и с какими проблемами столкнулись — надеемся, это поможет правильно определиться с выбором нужного инструмента для ваших систем.

Почему мы выбрали Cassandra

Дисклеймер: в контексте этой статьи и Cassandra, и ScyllaDB — почти синонимы, потому что с логической точки зрения это одно и то же: ScyllaDb — это по сути Cassandra, переписанная на C++ и работающая значительно быстрее. Но в плане архитектуры различий практически нет.

Обычно эти БД выбирают, когда нужны:

  1. Горизонтальная масштабируемость. Это одно из ключевых достоинств Cassandra. Можно наращивать систему, просто добавляя новые ноды. Теоретический предел есть, но он достаточно далек для большинства задач. При ребалансировке данных между узлами, конечно, наблюдается нагрузка на ресурсы, но это можно контролировать через параметры конфигурации.

  2. Высокая доступность и отказоустойчивость. С Cassandra можно гибко настраивать количество реплик и их распределение по узлам и дата-центрам. Это позволяет строить устойчивую инфраструктуру.

  3. Поддержка распределенной архитектуры (multi-datacenter). Один из самых сильных аспектов Cassandra — это стабильная работа с геораспределенными кластерами. В моем проекте инсталляции находились в дата-центрах, удаленных на десятки и даже сотни километров. Система работала стабильно благодаря выбору для каждого запроса своего уровня консистентности: от максимально доступного до строго согласованного.

Но есть и более сложные и расширенные преимущества Cassandra, с которыми знакомишься уже на практике.

Настройка консистентности в Cassandra

Согласно CAP-теореме, Cassandra по умолчанию ориентирована на доступность (Availability) и устойчивость к разделению системы (Partition Tolerance). При сбое части кластера система продолжает обслуживать запросы, жертвуя при этом строгой согласованностью данных.

Однако в Cassandra предусмотрено управление консистентностью на уровне каждого отдельного запроса. Например, если нужно гарантировать запись данных в заданное количество реплик, можно использовать такие уровни, как QUORUM или LOCAL_QUORUM. В последнем случае кворум определяется в рамках одного дата-центра, что снижает сетевые задержки и повышает отказоустойчивость в пределах локации.

Для чтения можно использовать уровень LOCAL_ONE, при котором клиент получает данные от первой доступной ноды, обладающей нужной копией. Это обеспечивает быструю реакцию системы, но не дает гарантии, что будет прочитано именно то, что было записано при предыдущем кворуме.

Если же требуется строгое соответствие между записанными и читаемыми данными, рекомендуется использовать LOCAL_QUORUM и на запись, и на чтение. Такая схема позволяет добиться уверенной согласованности, оставаясь при этом в рамках распределенной архитектуры.

Эти гибкие настройки дают Cassandra преимущество перед реляционными БД, где такие уровни управления консистентностью, как правило, недоступны — данные пишутся строго в мастер, а читаются со слейвов, без возможности тонкой настройки поведения в момент запроса.

Геораспределенная топология: гибкость на уровне реплик и дата-центров

Cassandra поддерживает несколько моделей топологии кластеров. По умолчанию используется простая стратегия, но для геораспределенных сценариев чаще применяют Network Topology Strategy. Она позволяет гибко задать количество реплик в каждом дата-центре индивидуально — в зависимости от задач, нагрузки и требований к отказоустойчивости.

Например, можно в одном дата-центре хранить по две реплики, в другом — по три, а в третьем ограничиться одной. Это позволяет выстраивать инфраструктуру под конкретные задачи.

Один из наших рабочих примеров: приложение должно было функционировать в двух полноценных дата-центрах. Соответственно, мы подняли кластеры в каждом из них. Дополнительно развернули еще по одному меньшему кластеру, которые использовались исключительно для организации полнотекстового поиска поверх данных.

Отдельно выделили сервер в третьем ДЦ и настроили на нем репликацию с фактором один. Цель этого узла — исключительно резервное копирование (бэкапы).

Такие схемы реализуются стандартными средствами Cassandra — при условии правильной настройки они показали свою надежность.

Производительность записи и отсутствие блокировок при изменении структуры

Запись данных в Cassandra устроена поэтапно. Сначала данные попадают в commit log, затем — в memtable, после чего сохраняются на диск в виде SSTable. Такой подход позволяет системе обеспечивать высокую скорость записи без необходимости блокировки таблиц.

В отличие от реляционных баз данных, таких как MySQL или MariaDB, где изменение структуры таблиц часто сопровождается полной блокировкой операций, в Cassandra таких проблем нет. В реляционной модели для внесения изменений в схему обычно создается новая таблица, в которую параллельно переносятся данные, после чего осуществляется переключение. Это не только трудоемко, но и чревато простоем при больших объемах.

В Cassandra подобные операции происходят без блокировок благодаря особенностям ее архитектуры. Это делает ее удобной для сценариев, где важны непрерывная доступность и возможность вносить изменения в схему без остановки обслуживания.

К чему надо быть готовым при работе с Cassandra

У этой БД есть и свои существенные ограничения, о которых стоит знать заранее.

Низкая отказоустойчивость на виртуальных машинах

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

Сейчас у нас 24 bare-metal-ноды, и в такой конфигурации все нормально. Чтобы был понятен масштаб: на одно кольцо приходится около 20 000 RPS на чтение и 10 000 на запись. С Cassandra при таких нагрузках «железо» чувствует себя достаточно хорошо даже при размере баз более 2 Тб, но любые отклонения от best practices моментально ощущаются в продакшне.

Работа с Cassandra в распределенной архитектуре требует четкого понимания сетевых ограничений и принципов построения колец. Формально, конечно, можно растянуть одно кольцо между несколькими дата-центрами. Но на практике это крайне не рекомендуется: подобная конфигурация приведет к постоянным сетевым сбоям, деградации производительности и нестабильному поведению кластера. Такой подход — для самых отчаянных.

Наше решение проблемы

Мы пошли другим путем: используем три независимых кольца, по одному на дата-центр. Каждое построено с учетом кворума и работает независимо, а между ними существуют только синхронизирующие перемычки. Они обеспечивают согласованность колец, но не участвуют в боевой нагрузке напрямую.

Что важно — при необходимости мы можем отключить одно из колец, и система продолжит работу. После восстановления кольцо постепенно догоняет остальные по данным благодаря механизму репликации, настройкам консистентности и replication factor.

Но все это требует глубокого понимания устройства Cassandra, особенно в части того, как работают кворумы, как правильно выставлять уровни консистентности для чтения и записи и какие компромиссы возможны в конкретной архитектуре. Без этого высок риск потерять данные или получить непредсказуемое поведение под нагрузкой. Если будет интересно, расскажем об этом следующим материалом — пишите в комментарии.

Repair — отдельная боль

Процедура repair — это штатный механизм работы Cassandra. Он необходим для поддержания согласованности данных между нодами и по-хорошему должен запускаться регулярно. Это не опция, а обязательный элемент эксплуатации.

Если вы приняли решение использовать Cassandra как key-value-хранилище, repair должен быть частью вашей регулярной операционной практики с самого начала. Без него кластер теряет устойчивость. И главное, важно сразу разобраться, как работает этот механизм и какие последствия он влечет в вашей архитектуре. Иначе — дорого, больно и долго.

При работе с большими объемами данных этот процесс становится критически уязвимым элементом инфраструктуры. Repair может привести к полной деградации кольца, особенно если выполняется под нагрузкой. Причем это происходит непредсказуемо.

Пример из практики: таблица объемом чуть меньше терабайта требовала регулярного repair. Один repair на кольце без нагрузки прошел штатно. Второй — под боевой нагрузкой — также завершился без инцидентов. Но третий — снова под нагрузкой — практически парализовал кольцо. Система формально оставалась доступной, но время отклика выросло с 200 миллисекунд до 20 секунд. Причины мы так и не установили: логов или метрик, однозначно указывающих на проблему, не было.

С подобным поведением мы сталкивались не раз — это повторяемая, системная особенность работы Cassandra при большом объеме данных и активной эксплуатации repair.

Мы в свое время недооценили эту необходимость. В нашей системе есть несколько keyspace, которые не подвергались repair почти год. В результате, когда понадобилось их синхронизировать, процесс занял двое суток. Все это время keyspace оставался фактически недоступным в рамках конкретного кольца.

Ограничения и антипаттерны Cassandra, о которых важно знать

На практике сложности при работе с этой БД начинаются, когда данные перестают быть просто key-value. Например, если нужно выяснить, какие токены и на каких устройствах активны, или требуется отслеживать срок жизни (expiration) определенных объектов. В этих сценариях интуитивно тянешься к дополнительным инструментам: пытаешься использовать materialized views, подключать secondary indexes, экспериментировать с их «реинкарнацией» — SASI (Storage-Attached Secondary Indexes). Но все это быстро превращается в источник боли.

Cassandra и ScyllaDB как ее наследница категорически не предназначены для реализации даже минимально реляционных сценариев. И это не субъективная жалоба — об этом прямо говорится в официальной документации.

Это общее правило для многих продуктов Apache, будь то Ignite, Kafka или сама Cassandra. Каждый из них отлично справляется только со своей узкой задачей. Но стоит попытаться расширить зону применения, возникают непредсказуемые последствия и требуются те еще архитектурные компромиссы.

Далее мы расскажем о самых сложных и болезненных моментах.

Самостоятельный repair secondary indexes

В документации четко указано: не используйте secondary indexes. Они были внедрены в ранних версиях как эксперимент, признаны неудачными, и команда проекта открыто написала: «Мы ошиблись, не повторяйте за нами».

Основная проблема с использованием secondary index заключается в том, что каждый такой индекс — это отдельная таблица. Ее тоже нужно компактифицировать, применять к ней repair, обслуживать. Она потребляет ресурсы почти так же, как основная таблица.

У secondary indexes есть критическая особенность: они могут внезапно запустить repair. Это встроенное поведение движка — системный процесс под капотом может в любой момент решить, что индекс требует синхронизации, и инициировать фоновый repair.

Этот процесс абсолютно непрозрачен и непредсказуем. Вы не поймете, когда он начнется, и не сможете его остановить. А самое неприятное — при определенных условиях он положит всю таблицу, к которой привязан индекс. В этот момент вы теряете возможность и читать, и писать в нее.

Фактически единственный способ восстановить работоспособность — удалить индекс целиком.

Secondary indexes в Cassandra — это по-прежнему проблемная зона. Использовать их не рекомендуется, так как они часто становятся причиной нестабильной работы и приводят к ухудшению производительности. Это ограничивает сферу применения Cassandra и требует проектировать схему хранения строго под конкретные запросы, что явно прописано в документации.

Недавно появилась новая разработка — Storage-Attached Index (SAI). В отличие от классических secondary index, SAI встраивается непосредственно в SSTable и хранится рядом с данными, а не в отдельной структуре. Индексы распределены по всем узлам, что устраняет проблему локализации. Однако остаются открытыми вопросы по поводу производительности при широких запросах, затрагивающих множество партиций. На текущий момент очевидного способа эффективно решать такие задачи с помощью SAI нет.

Для сравнения, у нас был практический опыт работы с MySQL в распределенной инфраструктуре. Управлять такой СУБД в условиях нескольких дата-центров оказалось крайне сложно. В лучшем случае можно организовать репликацию по схеме «мастер — слейв», но при сбое мастера требуется вручную перераспределять роли, что фактически означает переконфигурацию всего кластера. Это неудобно и нестабильно.

Кроме того, в MySQL жесткая схема данных, и любые изменения требуют либо блокировки таблицы, либо дополнительных обходных решений. Эти проблемы мы также регулярно фиксировали.

В результате мы отказались от этой архитектуры и перешли на схему, описанную выше, основанную на Cassandra с продуманной топологией и минимальным набором зависимостей.

Materialized views

С materialized views ситуация не лучше. Если secondary index — это проблемная «вспомогательная таблица», то materialized view — тот же набор данных, синхронизируемый с основной таблицей.

Все боли аналогичны: ресурсоемкость, необходимость регулярного обслуживания, сложности с консистентностью, а главное — непредсказуемость поведения. В результате и secondary indexes, и materialized views в реальном продакшне чаще становятся источниками сбоев, чем решением задач.

Нестабильный и сложный мониторинг

Мы не видели ни одного стабильного и универсального экспортера для Cassandra (и тем более для Scylla), который не страдал бы от высокой кардинальности метрик. Любая попытка интеграции с мониторингом превращается в борьбу за ресурсы. От команды observability уже звучали претензии, но системно решить эту задачу пока не удалось: экспорт работает, но требует большого количества ресурсов и тонкой настройки.

Отсутствие auto_increment

Для многих сценариев, особенно в привычной реляционной парадигме, это может оказаться серьезным ограничением. В Cassandra нет механизма auto_increment-идентификаторов, и если приложение жестко завязано на последовательные ID, потребуется искать обходные архитектурные решения. Впрочем, большинство команд, использующих распределенные базы, как правило, уже ориентируется на другие схемы генерации ключей, не зависящие от последовательности.

Ограниченная поддержка транзакций

Полноценной транзакционности, как в реляционных СУБД, здесь нет. Да, существуют так называемые lightweight transactions, но они подходят только для узких кейсов и не гарантируют целостность данных в привычном понимании ACID. Поэтому использовать Cassandra для сценариев с банковскими или финансовыми транзакциями не рекомендуется — эта СУБД не про целостность, а про масштаб и отказоустойчивость.

Невозможность сложных запросов

Архитектура Cassandra не предусматривает выполнения запросов, характерных для реляционных моделей. Поэтому всю логику, связанную с формированием выборок, необходимо проектировать заранее, исходя из конкретных потребностей приложения.

Рекомендации разработчиков и документация (в том числе от DataStax) подчеркивают: подход к проектированию в Cassandra начинается с анализа запросов, а не структуры данных. Сначала определяется, какие запросы будут выполняться, затем на основе этого формируется структура таблиц. Это кардинально отличается от реляционного подхода, где структура проектируется независимо от запросов.

Резервное копирование: дорого, долго и крайне критично

Вопрос бэкапа в Cassandra — это не опция, а необходимость. Но при этом сам процесс — один из самых ресурсоемких и болезненных. Это отдельный процесс, который требует внимания, бюджета и постоянного мониторинга. А главное — реального плана восстановления, иначе в критической ситуации времени на размышления уже не будет.

В нашем случае объемы данных в Cassandra достигают десятков терабайт, и это делает восстановление крайне сложной задачей. Создание полного бэкапа занимает много времени и ресурсов, а процесс восстановления — еще больше. Поэтому ситуация, в которой приходится реально «поднимать кластер из бэкапа», означает, что случился серьезный системный сбой.

Бэкап у нас идет непрерывно: как только завершается одна операция, сразу начинается следующая. Это единственный способ хоть как-то соответствовать требованиям по RTO (Recovery Time Objective) и RPO (Recovery Point Objective). Но даже при такой схеме добиться приемлемых метрик непросто, особенно на фоне высокой нагрузки и распределенной архитектуры.

Решения архитектурных проблем

Решений здесь по сути два:

  • Самописные конструкции. Это означает ручное проектирование дополнительных таблиц и обходных схем для реализации логики, близкой к реляционной. Некоторые компании идут по этому пути: моделируют данные под конкретные запросы, создают кастомные представления, затачивают под кейсы. Но это сложный, дорогой и поддерживаемый только вручную подход, с массой ограничений и рисков.

  • Принять ограничения архитектуры Cassandra. Это key-value-хранилище, и оно не предназначено для работы с реляционными структурами и сложными запросами. Все, что требует JOIN-ов, фильтрации по произвольным полям и сложной агрегации, должно уходить в специализированные СУБД: PostgreSQL, MySQL и т. п.

Мы также всерьез рассматривали переход на CDC-модель (Change Data Capture) с использованием встроенного механизма Cassandra, который появился начиная с версии 4.1. Эта архитектура аккуратно выносит часть логики и анализа за пределы самой базы, не перегружая кластер лишними вычислениями и индексами. Мы ее оценили, но это тема уже для другого материала.

Что важно понимать при работе с Cassandra

При проектировании таблиц учитывайте наличие двух разных типов ключей: 

  • Партиционный ключ (partition key). Cassandra распределяет данные по партициям, которые логически организованы в кольцо. Каждая запись должна иметь уникальный ключ, определяющий партицию и узел кластера, куда попадет эта запись. Это обязательный элемент структуры таблицы.

  • Кластерный ключ (clustering key). Он работает внутри партиции и определяет порядок хранения и выборки данных. Обычно его выбирают с учетом того, по каким полям будет происходить сортировка и фильтрация внутри одной партиции.

В результате получается составной первичный ключ: PRIMARY KEY (partition_key, clustering_key). Запросы в Cassandra должны быть спроектированы так, чтобы сначала использовать partition key, — это позволяет системе обратиться сразу к нужной партиции, и при необходимости дополнительно — clustering key для уточнения выборки.

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

Применять или нет Cassandra и ScyllaDB в продакшне?

Если подводить итоги, то выше мы описали самые болезненные моменты, с которыми столкнулись за годы работы с этими БД. Надо учитывать наличие в команде соответствующей экспертизы: если ее нет, то будет очень больно эксплуатировать эти БД.

Нужно быть готовым к неожиданным проблемам, приводящим к продолжительной недоступности — на часы или даже сутки. При этом в большинстве случаев невозможно вмешаться: нельзя ускорить ни repair, ни compaction, ни восстановление после сбоев.

Особенно критична ситуация, если у вас нет выделенного резервного кольца, не участвующего в боевой нагрузке. Это дорого в плане «железа» и сопровождения, но такой подход действительно спасает при серьезных сбоях. Хорошей практикой считается конфигурация из двух рабочих колец и одного поддерживающего, которое можно использовать для восстановления и аналитики.

Наконец, нужно понимать, что существуют и более дешевые альтернативы key-value-хранилищам — как по стоимости инфраструктуры, так и по сложности эксплуатации. Но окончательное решение зависит от конкретного кейса и требований к архитектуре.

Комментарии (9)


  1. Tsimur_S
    19.08.2025 14:45

    Главная особенность и одновременно ограничение Cassandra и ScyllaDb — это то, что они строго key-value-хранилища. Именно с этим они справляются отлично — быстрое чтение и запись по ключу, георезервирование и масштабирование. На этом этапе все выглядит радужно.

    А сама Кассандра себя позиционирует как key-value? Имхо если просто запрашивать по ключу то любая база справится отлично, даже MySQL.

    Зачем только это все нужно если можно просто выкатить редис.


    1. diderevyagin
      19.08.2025 14:45

      Зачем только это все нужно если можно просто выкатить редис.

      Потому что нужен объем данных, который куда больше ОЗУ. Redis хорошо справляется там, где размер БД сильно меньше или сопоставим с размером ОЗУ. А когда надо условно хранить 100Тб в 3-х географически разнесенных ДЦ, тут и выходят на сцену


    1. atshaman
      19.08.2025 14:45

      Самое забавное, что нет ). Они база данных временных рядов (tabular или wide columns) БД - может, немного "гибридного дизайна" - но выбор в качестве kvstore и впрямь, странный.


  1. Anakin2465
    19.08.2025 14:45

    используем три независимых кольца, по одному на дата-центр

    У вас три отдельных кластера или внутри одного кластера вы разбили token range на 3 кольца? Типа 8 тачек 1 кольцо? Вы "руками" расчитывали token range, а не использовали num_tokens или как?
    Как вы repair выполняете? С помощью cassandra-reaper или через nodetool? Инкрементальный или full? Пару дней для full repair не так уж и много, как по мне.
    Какую версию cassandra вообще используете? Пробовали уже 5, хотите прод на 5 обновлять?
    Про бэкап тоже расскажите, по подробнее :) Где снепшоты храните и тд и тп


    1. LdEsT Автор
      19.08.2025 14:45

      8 тачек - 1 кольцо, всего 24. рэнджи руками выставляли, все верно. используем reaper с фуллом, до инкрементального пока не доросли:) сейчас 4.0.1, апаем до 4.1.9, потом будем трогать 5ю. бэкап - отдельная боль, связанная с объемами и RTO/RPO. думаю, об этом будет отдельная статья


      1. Anakin2465
        19.08.2025 14:45

        Выходит, что у Вас кластер с 3мя DC с vnodes1 и распределенными руками токен ренжами? А почему вы решили, что это для Вас лучшее архитектурное решение? Почему вы не доверили распределение токен ренжей внутри DC самой cassandra, cделав 4 или 8 vnodes?


        1. LdEsT Автор
          19.08.2025 14:45

          это хороший вопрос, спасибо. выглядит так, что это исторически сложившееся, что стоит переосмыслить


  1. atshaman
    19.08.2025 14:45

    Ну, с учетом того, что cassandra - ниочень-то key-value database, а скорее wide columns\tabular database - претензий должно быть не мало...


  1. ebt
    19.08.2025 14:45

    Дорого ли обошлась вам Scylla? Если возможно, упомяните хотя бы порядок.