— Не бойтесь, она не кусается.
— Что, сразу целиком проглатывает?

Критериям выбора архивного хранилища она соответствовала идеально. Оптимизированная под запись, легко масштабируемая, совместимая с привычной уже Cassandra, только в разы быстрее… Имя же её — Сцилла (греч. Σκύλλα — «лающая») — напоминая о мифологическом чудовище, рисовало в воображении картины молниеносного поглощения гигантских объемов данных. Сложно было устоять и не попробовать.

На тот момент мы уже использовали MongoDB и некоторые другие решения для реализации высоконагруженной товароучётной системы. Объём информации исчислялся терабайтами и продолжал стремительно расти. Однако значительную его часть составляли данные товаров, завершивших свой жизненный цикл. Появилась идея переносить такие данные в отдельное долговременное хранилище — архив. Вероятность появления запросов на чтение этой информации почти равна нулю. Почти! И всё же нулевой не является, поэтому отправлять такую информацию в /dev/null нельзя. Но и рабочую базу загромождать практически бесполезными данными тоже неправильно. Вот и получается, что архив — самое подходящее для таких данных место. Важно только, чтобы скорость записи в архив была сопоставима со скоростью перехода товаров в конечное состояние в основном хранилище. Что ж, вызов принят!

Изучив отзывы по нескольким популярным NoSQL решениям, мы решили попробовать Сциллу. Слишком уж заманчиво про неё рассказывали пользователи, да и исследовательский азарт прямо-таки требовал попробовать что-то новенькое.

Что-то новенькое

Сцилла — колоночная база данных, написанная на C++, с собственным алгоритмом утилизации памяти и CPU. При этом пользователь, как оказалось, практически не имеет механизмов воздействия на управление этим алгоритмом. К примеру, Сцилла не позволяет варьировать количество потоков под операцию компактификации — в отличие от той же Cassandra, идентичность интерфейсов с которой заявлена в качестве важного преимущества новой базы данных. Иначе говоря, когда чудовище наедается до определённой степени, оно переключает свои силы — зачастую все — на переваривание данных. И пусть весь мир подождёт.

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

Что интересно, хотя Сцилла и написана на С++, основная утилита для работы с ней — nodetool — реализована на Java, поэтому администрирование БД упирается по производительности и памяти в ограничения JVM. С чем мы не раз сталкивались, например, при удалении машины из кластера, когда операция nodetool cleanup не могла доработать до конца по причине нехватки памяти для JVM.

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

Что будем архивировать и куда

В нашем случае данные, предназначенные для архивации, имели следующий вид:

  • product_id (String): "50054c8b6f87f912"

  • exec_date (Timestamp): 2022-05-31T00:00:00.000+0000

  • owner (String): "12e8ea62818112e8"

  • states (Object): {…}

Ключом записи, по которому её можно было однозначно идентифицировать, служил атрибут product_id.

Для развертывания Сциллы была сконфигурирована следующая инфраструктура:

  • количество серверов: 5;

  • количество CPU на сервере: 32;

  • размер ОП на сервере (Гб): 312;

  • тип жестких дисков: HDD.

Хотя для достижения максимальной производительности разработчики Сциллы рекомендуют использовать SSD диски, опыт пользователей показал, что и с HDD дисками можно добиться приемлемой скорости записи. Поэтому мы решили начать с HDD-варианта, как более экономичного, рассматривая переход на SSD в качестве резервного решения.
Также, руководствуясь рекомендациями разработчиков Сциллы (и здравым смыслом) и исходя из наличия пяти доступных серверов, установили фактор репликации равный трём. На серверах развернули keyspace — логическое объединение данных (рис. 1).

Рис. 1
Рис. 1

Для описания структуры данных, а также для манипулирования данными в Сцилле используется CQL (Cassandra Query Language). Команды на нем напоминают аналогичные команды эталонного своей полнотой SQL и выполняются в специальной утилите cqlsh. Чтоб создать keyspace с именем archive и фактором репликации 3, можно воспользоваться следующей командой:

CREATE KEYSPACE archive WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true;

Для хранения направляемых в архив записей мы добавили в keyspace таблицу products, подобрав типы данных из тех, что поддерживаются Сциллой, и указав компрессию. Последняя определяется алгоритмом и степенью сжатия, которые будут применяться к сохраняемым данным. Учитывая запланированную интенсивность архивации и долгий срок хранения данных, решили сразу задать максимальную компрессию (алгоритм Zstandard, уровень сжатия 22). Поскольку Сцилла поддерживает шардирование данных, что называется, «из коробки», для таблицы необходимо задать ключ партиции (partition key). Он используется для расчёта токена и определения шарда для хранения этой партиции. В нашем случае партиция (partition) однозначно соответствовала записи (row), и ключом партиции стал product_id (рис. 2).

Рис. 2
Рис. 2

Таблица products с указанными характеристиками в keyspace archive создается следующей командой CQL:

CREATE TABLE archive.products (product_id text PRIMARY KEY, exec_date timestamp, owner text, states text) WITH compression = {'sstable_compression': 'ZstdCompressor', 'compression_level': 22, 'chunk_length_in_kb': 64};

Ну и на случай поиска по неключевым атрибутам, хоть он и не планировался в ближайшей перспективе, мы решили повесить индексы на атрибуты exec_date и owner (как показало дальнейшее развитие событий, совершенно напрасно):

CREATE INDEX ON archive.products(exec_date);
CREATE INDEX ON archive.products(owner);

Скорость перехода товаров в конечное состояние (т.е. появление данных, пригодных для архивации) составляла порядка 30 млн. единиц в сутки. Ожидалось, что скорость архивации будет не меньше, т.к. уже имелся определенный бэклог, который требовалось разобрать. Разумеется, первым делом мы сделали cassandra-stress тест, чтоб определить, можно ли вообще рассчитывать на приемлемую скорость архивации для получившейся модели данных и записи заданного размера. А заодно проверили, какой эффект может дать отправка записей не поодиночке, а пачками.

Тест для одиночных записей показал такие скорости:

  • Op rate (скорость выполнения операций): 2,431 op/s;

  • Row rate (скорость добавления записей): 2,431 row/s;

  • Latency mean (средняя задержка операции): 9.8 ms.

Для архивации же пачками (по 100 записей за операцию) результат был такой:

  • Op rate (скорость выполнения операций): 46 op/s;

  • Row rate (скорость добавления записей): 4,634 row/s;

  • Latency mean (средняя задержка операции): 516.1 ms.

Хотя во втором случае скорость выполнения операций была существенно ниже (что неудивительно, т.к. каждая операция включала в себя вставку сразу ста записей), записи добавлялись почти вдвое быстрее. Этот вариант мы и выбрали: 4634 записи в секунду (а это порядка 400 млн. записей в сутки) с избытком обеспечивали требуемую скорость архивации, оставляя запас на сбор данных из основного хранилища и отправку данных по сети.

И, главное, как архивировать…

Результаты стресс-теста радовали и мотивировали на продолжение исследований. Через какое-то время был готов алгоритм архивации. Он учитывал три важных аспекта.

  1. Поскольку Сцилла хранит записи в неизменяемых файлах SSTables (Sorted Strings Tables), ей необходимо периодически проводить операцию компактификации, в ходе которой актуальные копии записей заносятся в новые SSTables, а старые SSTables удаляются. Компактификация, как уже отмечалось выше, значительно снижает производительность Сциллы. Поэтому после копирования в последнюю ограниченного объема данных, ей нужно дать время на «переваривание».

  2. Прежде чем удалять заархивированные данные из основного хранилища, неплохо бы убедиться, что они действительно записались в архив, т.к. Сцилла — для нас база новая, неизученная, и мало ли…

  3. Удаление данных из основного хранилища замедляет все остальные операции с этим хранилищем, поэтому выполнять его следует строго в отведенное время, когда запросов к основному хранилищу немного.

С учетом этих аспектов алгоритм архивации разделялся на три этапа: копирование, валидация (от этого шага планировали со временем отказаться) и удаление (рис. 3).

Рис. 3
Рис. 3

Скорость копирования с учётом всех накладных расходов поначалу составляла 138 млн. записей в сутки — это казалось более чем достаточным результатом, хотя отставание от результатов cassandra-stress теста (400 млн. записей в сутки) было весьма значительным. Но ещё оставались этапы валидации и удаления, которые тоже занимали время, превращая суточный результат в недельный. И по факту получалось ~20 млн. записей в сутки — меньше скорости перехода товаров в конечное состояние (30 млн. в сутки). Однако, мы сохраняли оптимизм, предполагая, что недостаток скорости получится преодолеть путем отказа от этапа валидации, если система окажется стабильной…

Но всё что-то пошло не так

Как только объём заархивированных данных перевалил за терабайт, посыпались проблемы. Во-первых, более 50% операций записи в Сциллу стали завершаться по таймауту. Сцилла не успевала ни записать, ни «переварить» отправленные в неё данные. В итоге скорость копирования деградировала более чем в 5 раз: со 138 до 26 млн. в сутки. А скорость архивации в целом упала даже не до 3 млн. в сутки, а ещё значительней из-за сильного замедления этапа валидации.

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

large_data - Writing a partition with too many rows [products /products_exec_date_idx_index:00000000146428] (294334 rows)

для Materialized View, на основе которых были созданы наши индексы для exec_date и owner. К сожалению, «лучшее» решение, предлагаемое разработчиками Сциллы для распространенной, кстати, проблемы — не использовать таблицы с большим числом записей на партицию. Иначе говоря, от индексов, которые нам, по счастью, так и не пригодились, пришлось отказаться. Да, в нашем случае индексы действительно были балластом, но потенциальным пользователям Сциллы стоит учитывать этот существенный недостаток, который так и не был исправлен её разработчиками на момент написания данной статьи.

В-третьих, почти с самого начала эксперимента рассматривалась возможность ускорения Сциллы за счет переезда на SSD. И вот, когда возможность переросла в потребность, мы таки провели эксперимент с архивацией на SSD. Но к нашему великому разочарованию проблемы с таймаутами никуда не делись, да и особого прироста в скорости записи не получилось. Более того сам процесс переезда на SSD принес множество сюрпризов, борьба с которыми вполне заслуживает отдельной статьи.

Если в двух словах, то не стоит полагаться на документацию. Например, у Сциллы есть конфигурационный параметр auto_bootstrap, от которого во многом зависит, будет ли добавляемая в кластер машина с пустой базой подтягивать данные с других машин кластера (auto_bootstrap = True) или сама станет источником данных (auto_bootstrap = False), что приведёт к затиранию информации на всём кластере. Документация гласила, что по умолчанию, auto_bootstrap = True, но… не уточняла, с какой версии. Угадайте, повезло ли нам?) Казалось, эксперимент со Сциллой зашёл в тупик.

Но мы не сдались

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

ALTER TABLE archive.products WITH compression = {'sstable_compression': 'LZ4Compressor', 'chunk_length_in_kb': 16};

мы заметили определенный прогресс. Число таймаутов при копировании сократилось до нуля, возросла скорость копирования (до ~200 млн. записей в сутки) и компактификации. Теперь Сцилла даже позволяла проводить компактификацию параллельно с копированием.
Отсутствие таймаутов позволило нам отказаться от этапа валидации, нагружавшего запросами на чтение отнюдь не приспособленную для этого Сциллу. Новый алгоритм архивации стал значительно проще (рис. 4).

Рис. 4
Рис. 4

Однако скорости копирования и удаления данных были ещё далеки от идеала. И профилирование показывало, что проблема теперь уже касалась основного хранилища — MongoDB. Здесь проблема частично решилась с переездом на шардированное хранилище, но это уже совсем другая история. Применение означенных решений позволило нам в итоге добиться скорости архивации (да-да, именно архивации!) ~300 млн. записей в сутки, что значительно превышало требуемые значения (рис.5).

Рис. 5
Рис. 5

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

Нет предела совершенству

Да, в работе со Сциллой есть свои особенности. Некоторые из них я упомянул в статье, но это лишь вершина айсберга, и большая часть наверняка осталась незатронутой. И если их знать и учитывать, то вполне можно рассматривать Сциллу, как минимум, в качестве более производительной замены Cassandra. А как максимум… Тут всё зависит от конкретной задачи.

Что же касается нашего исследования, то оно, конечно, продолжится, несмотря на то что требуемые проектные показатели достигнуты. Ещё стоит задача по выявлению оптимальной компрессии и переходу к ней, т.к. диски жёсткие, но, увы, не резиновые. Есть что ускорить и в алгоритме архивации. Да и в самой Сцилле можно много чего исправлять и совершенствовать. К примеру, зависимость быстрого плюсового решения от джавовых административных утилит вроде nodetool или dsbulk — да ещё и с урезанным функционалом — постепенно становится, на мой взгляд, тормозящим фактором. Понятно, что схожесть с Cassandra на начальных порах способствовала переходу на новую базу данных во многих компаниях, но если Сцилла всё же стремится перерасти своего ближайшего конкурента, стоит оторваться от него посильнее.

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

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


  1. AlexSpaizNet
    02.06.2022 15:25
    +1

    Рассматривали ли вы облачные решения, как например snowflake?


    1. avtozavodetz Автор
      02.06.2022 16:58

      Да, есть много облачных решений, в том числе и у Сциллы. Но всё-таки хотелось иметь больший контроль над хранилищем (вплоть до физического доступа). Это особенно важно при испытании новой технологии, чтоб быть уверенным, что проблема не в ресурсах облачного провайдера, а в неправильных действиях испытателя. Ну и доступ к качественным облачным сервисам сейчас сопровождается определенными рисками. Поэтому мы изначально были нацелены на использование именно on-premise решения.


  1. kirik
    03.06.2022 06:37
    +1

    А расскажите пожалуйста, на каком железе работала монги и какой рпс она держала? Интересно в сравнении на вашем типе нагрузки. Ибо в своё время тоже рассматривал сциллу/кассандру как замену кластеру монги, но спустя неделю тестов каждой не получилось даже приблизиться к монговским рпс на запись (при +/- одинаковом железе).


    1. avtozavodetz Автор
      03.06.2022 12:11
      +1

      Наш кластер Mongo работал и работает фактически на таком же железе, какое получила Scylla, и обеспечивает rps порядка 40K. Как показала практика, Scylla не может служить полноценной заменой Mongo. Это связано не только с тем, что Scylla уступает по производительности, но и с указанными в статье нюансами. При этом компактность хранения данных и простота масштабирования хранилища всё же позволяют рассматривать Scylla для задач долговременного хранения и как вариант диверсификации зависимостей системы.


      1. kirik
        03.06.2022 16:11
        +1

        Расскажите пожалуйста про компактность хранения, какой выйгрыш получился при сравнимом объеме документов?

        При этом компактность хранения данных и простота масштабирования хранилища всё же позволяют рассматривать Scylla для задач долговременного хранения и как вариант диверсификации зависимостей системы.

        Рассматривали ли вы вариант реализацие tiered хранилища на монге (используя тэги при шардировании)? Или второй кластер монги под холодные данные. Это я к чему все.. Вы взяли незнакомую БД под архивные данные, хотя текущая БД доказала надежность и с её помощью можно реализовать hot-cold архитектуру данных. Хочу понять мотивацию :)


        1. avtozavodetz Автор
          03.06.2022 18:09
          +1

          Речь о выигрыше не идёт. Достаточно того, что Sclylla просто поддерживает действенные алгоритмы сжатия. И, конечно же, аналогичное архивное хранилище можно реализовать и при помощи Mongo. Что же касается мотивации исследования... Это стремление уменьшить зависимость всей системы от одной конкретной технологии, а заодно выяснить, является ли новое решение более эффективным, чем старое. Ведь это хорошо, когда есть альтернатива, не так ли? :)


          1. kirik
            03.06.2022 20:00
            +1

            Спасибо за ответ и за статью!


  1. maxim_ge
    05.06.2022 00:28

    2431 ops - как-то маловато. Да ещё и latency 9.8, странно. А каков общий размер записи, примерно?