Говорят, в жизни все стоит попробовать хотя бы раз. И если вы привыкли работать с реляционными СУБД, то познакомиться на практике с NoSQL стоит в первую очередь хотя бы для общего развития. Сейчас в силу бурного развития этой технологии очень много противоречивых мнений и горячих споров на эту тему, что особенно подогревает интерес.
Если вникнуть в суть всех этих споров, то можно увидеть, что они возникают из-за неправильного подхода. Те, кто использует NoSQL базы именно там, где они нужны, довольны и получают от данного решения все его плюсы. А экспериментаторы, уповающие на данную технологию как панацею там, где она не применима вовсе, испытывают разочарование, потеряв сильные стороны реляционных баз без приобретения весомых выгод.
Я расскажу про наш опыт внедрения решения, основанного на СУБД Cassandra: с чем пришлось столкнуться, как выкручивались из трудных ситуаций, удалось ли нам получить выигрыш от использования NoSQL и где пришлось вложить дополнительные усилия/средства.
Исходная задача — это построение системы, записывающей звонки в некое хранилище.
Принцип действия системы следующий. На вход приходят файлы с определенной структурой, описывающей структуру вызова. Затем приложение обеспечивает сохранение этой структуры в соответствующие колонки. В дальнейшем сохраненные вызовы используются – для отображения информации по потреблению трафика для абонентов (начисления, звонки, история баланса).
Почему выбрали Кассандру вполне понятно — она пишет как пулемет, легко масштабируема, отказоустойчива.
Итак, вот что нам подарил опыт
Да, вылетевшая нода — не трагедия. В том и суть отказоустойчивости Кассандры. Но нода может быть живой и при этом начать проседать по производительности. Как выяснилось, это сразу отражается на производительность всего кластера.
Кассандра не подстрахует там, где Oracle спасал своими констрейнтами. И если автор приложения заранее этого не понял, то прилетевший дубль для Кассандры ничуть не хуже оригинала. Раз пришел, значит вставим.
Бесплатная Кассандра «из коробки» резко не понравилась ИБ: логирования действий пользователей нет, разграничения прав тоже. Информация о вызовах относится к персональным данным, а это значит, что все попытки каким-либо образом ее запросить/изменить, должны журналироваться с возможностью последующего аудита. Также, нужно осознавать необходимость разделять права по разным уровням для разных пользователей. Простой инженер эксплуатации и суперадмин, который свободно может удалить весь keyspace – это разные роли, разная ответственность, компетенция. Без такого разграничения прав доступа ценность и целостность данных сразу окажется под вопросом быстрее, чем при уровне консистентности ANY.
Мы не учли, что по звонкам требуется как серьезная аналитика, так и периодические выборки по самым разным условиям. Так как выбранные записи потом предполагается удалять и перезаписывать (в рамках задачи мы должны поддерживать процесс актуализации данных при изначально неверно поступивших к нам в контур данных), то Кассандра нам здесь не товарищ. Кассандра, как копилка – в нее удобно складывать, но в ней не получится считать.
Столкнулись с проблемой переноса данных в тестовые зоны (5 нод в тесте против 20 в проме). Дамп в таком случае использовать не получится.
Проблема обновлений схемы данных приложения, пишущего в Кассандру. Откат породит великое множество надгробий, что непредсказуемым образом может нам просадить производительность. Кассандра оптимизирована для записи, и перед тем, как записать, много не думает Любая операция с существующими данными в ней – это тоже запись. Т. е. удалив лишнее, мы просто наплодим еще больше записей, и только часть из них будет помечена надгробиями.
Таймауты при вставке. Кассандра в записи прекрасна, но иногда входящий поток может ее существенно озадачить. Такое происходит, когда приложение начинает гонять по кругу несколько записей, которые не удается вставить по какой-либо причине. И нам нужен будет вполне себе настоящий ДБА, который станет следить за gc.log, логи system и debug на предмет slow query, метрики на compaction pending.
Несколько датацентров в кластере. Откуда читать и куда писать?
Возможно, разделить на чтение и на запись? И если да, то ближе к приложению должен быть ДЦ для записи или для чтения? И не получится ли у нас настоящий split brain, если неверно выберем уровень согласованности? Очень много вопросов, много неизведанных настроек, возможностей, которые так хочется покрутить.
Как мы решали
Чтобы нода не просаживалась, отключили SWAP. И теперь при нехватке памяти нода должна лечь, а не плодить большие gc паузы.
Итак, на логику в БД мы больше не надеемся. Разработчики приложения переучиваются и начинают активно подстраховываться в своем же коде. Идеальное четкое разделение хранения и обработки данных.
Покупали поддержку от DataStax. Коробочную Кассандру уже перестали разрабатывать (последний коммит в феврале 2018 года). В то же время, Datastax предлагает отличный сервис и большое количество доработанных и адаптированных под существующие ИС решений.
Ещё хочу отметить, что Кассандра не очень удобна для запросов на выборку. Разумеется, CQL — большой шаг навстречу пользователям (по сравнению с Trift). Но если у вас есть целые отделы, привыкшие к таким удобным джойнам, свободной фильтрации по любому полю и возможностям оптимизации запроса, и эти отделы работают над тем, чтобы закрывать претензии и аварии, то решение на Кассандре, видится им вражеским и глупым. И мы начали решать вопрос, как нашим коллегам делать выборки.
Рассматривали два варианта.В первом варианте пишем вызовы не только в С*, но и в архивную БД Oracle. Только в отличие от C* в этой БД хранятся вызовы лишь за текущий месяц (достаточная глубина хранения вызовов для кейсов перетарификации). Здесь сразу виделась следующая проблема: если писать синхронно, то мы теряем все плюсы С*, связанные с быстрой вставкой, если асинхронно – нет гарантии того, что все нужные вызовы вообще попали в Oracle. Плюс был один, но большой: для эксплуатации остается все тот же привычный PL/SQL Developer, т. е. практически реализуем паттерн «Фасад».Альтернативный вариант. Реализуем механизм, который выгружает вызовы из C*, тянет какие-то данные для обогащения из соответствующих таблиц в Oracle, джойнит полученные выборки и выдает нам полученный результат, который мы потом как-то используем (откатываем, переповторяем, анализируем, восхищаемся). Минусы: процесс получается довольно многошаговым, а кроме того, отсутствует интерфейс для сотрудников эксплуатации.
В итоге остановились все же на втором варианте. Для выборок из разных банок использовали Apache Spark. Суть механизма свелась к Java-коду, который по указанным ключам (абонент, время совершения вызова – ключи раздела) вытаскивает данные из C*, а также нужные данные для обогащения из любой другой БД. После чего джойнит их в своей памяти и выводит результат в результирующую таблицу. Над спарком нарисовали веб-мордочку и получилось вполне пригодно к эксплуатации.
При решении задачи с обновлением данных пром-тест опять рассматривали несколько способов решения. Как перенос через Sstloader, так и вариант с разбиением кластера в тестовой зоне на две части, каждая из которых попеременно входит в один кластер с промовским, запитываясь таким образом от него. При обновлении теста планировалось менять их местами: та часть, которая работала в тесте, очищается и вводится в пром, а другая начинает работать с данными отдельно. Однако, подумав еще раз, мы более рационально оценили те данные, которые стоит переносить, и поняли, что сами по себе вызовы – неконсистентная сущность для тестов, быстро генерируемая в случае необходимости, и именно промовский набор данных не имеет ценности для переноса в тест. Есть несколько объектов-накопителей, которые стоит переносить, но это буквально пара таблиц, причем, не сильно тяжелых. Поэтому нам в качестве решения опять пришел на помощь Spark, с помощью которого мы написали и стали активно использовать скрипт переноса данных между таблицами пром-тест.
Наша текущая политика деплоя позволяет нам работать без откатов. Перед промом идет обязательный накат на тест, где ошибка не так дорога. В случае неудачи всегда можно дропнуть кейспейс и накатить всю схему с начала.
Для обеспечения непрерывной доступности Кассандры нужен дба и не только он. Все, кто работает с приложением, должны понимать, где и как смотреть текущую ситуацию и как своевременно диагностировать проблемы. Для этого мы активно используем DataStax OpsCenter (Администрирование и мониторинг рабочих нагрузок), системные метрики Cassandra Driver (кол-во таймаутов на запись в C*, кол-во таймаутов на чтение из C*, максимальная latency и т. д.), мониторим работу самого приложения, работающего с Кассандрой.
Когда задумались над предыдущим вопросом, то поняли, где у нас может крыться основной риск. Это — формы отображения данных, которые выводят данные из нескольких независимых друг от друга запросов к хранилищу. Таким образом мы можем получить довольно несогласованную информацию. Но эта проблема была бы так же актуальна и в случае, если бы мы работали только с одним датацентром. Так что самое разумное здесь – это, конечно, делать батчевую функцию чтения данных на стороннем приложении, которая обеспечит получение данных в единый период времени. Что касается разделения на чтение и запись в плане производительности, то здесь нас остановил риск того, что при некоторой потере коннекта между ДЦ, мы можем получить два совершенно несогласованных между собою кластера.
В итоге, на данный момент остановились на уровне согласованности для записи EACH_QUORUM, для чтения – LOCAL_QUORUM
Краткие впечатления и выводы
Для того, чтобы оценить получившееся решение с точки зрения эксплутационной поддержки и перспектив дальнейшего развития, мы решили подумать, где ещё можно применить такую разработку.
Если с ходу, то скоринг данных для программ типа «Плати, когда удобно» (грузим в С* информацию, расчет на скриптах Spark), учет претензионки с агрегацией по направлениям, хранение ролей и вычисление по ролевой матрице прав доступа пользователей.
Как видим, репертуар широк и разнообразен. И если выбирать лагерь сторонников/противников NoSQL, то мы примкнем к сторонникам, так как свои плюсы получили, и именно там, где и ожидали.
Даже вариант Кассандры из коробки позволяет производить горизонтальное масштабирование в реалтайме, абсолютно безболезненно решая вопрос увеличения данных в системе. У нас получилось вынести в отдельный контур очень высоконагруженный механизм по расчету агрегатов по звонкам, а также, разделить схему и логику приложения, избавившись от порочной практики написания кастомных джобов и объектов в самой БД. Мы получили возможность выбирать и настраивать, для ускорения, на каких ДЦ мы будем производить расчет, а на каких запись данных, подстраховали себя на предмет падений как отдельных нод, так и в целом ДЦ.
Применяя к новым проектам нашу архитектуру, причем, уже обладая некоторым опытом, хотелось бы сразу же учитывать описанные выше нюансы, и не допустить некоторых ошибок, сгладить некие острые углы, которых не удалось избежать изначально.
Например, вовремя отслеживать обновления самой Кассандры, потому что довольно много проблем, которые мы получили, уже были известны и правились.
Не сажать и саму БД, и Спарк на одни и те же ноды (или строго разделять по количеству допустимого использования ресурсов), так как Спарк может съесть ОП больше положенного, и мы быстро получим проблему номер 1 из нашего списка.
Прокачивать мониторинг и компетенцию эксплуатации еще на этапе тестирования проекта. Изначально учитывать по максимуму всех потенциальных потребителей нашего решения, потому что именно от этого будет зависеть в итоге структура БД.
Несколько раз покрутить получившуюся схему на предмет возможной оптимизации. Выделить какие поля можно сериализовать. Понять какие дополнительные таблицы нам сделать, чтобы наиболее корректно и оптимально учитывать, и потом отдавать по запросу требуемую информацию (например, допуская, что одни и те же данные мы можем хранить в разных таблицах, с учетом разной разбивки по разным критериям, можно существенно экономить процессорное время при запросах чтения).
Неплохо сразу предусмотреть навешивание TTL и чистку устаревающих данных.
При выгрузке данных из Кассандры логика приложения должна работать по принципу FETCH, чтобы не все строки загружались в память за один раз, а выбирались пачками.
Желательно перед переводом проекта на описываемое решение проверить отказоустойчивость системы, проведя серию краш-тестов, вроде потери данных в одном ЦОДе, восстановления испорченных данных за какой-то период, просадки сети между ЦОДами. Такие тесты не только позволят оценить плюсы и минусы предлагаемой архитектуры, но и дадут хорошую разминочную практику проводящим их инженерам, а полученный навык будет далеко не лишним, если отказы системы воспроизведутся в проме.
Если мы работаем с критичной информацией (такой как данные для проведения биллинга, расчет задолженности абонента), то стоит также уделить внимание инструментам, которые позволят снизить риски, возникающие в силу особенностей СУБД. Например, использовать утилиту nodesync (Datastax), выработав оптимальную стратегию ее использования, чтобы ради консистентности не сформировать избыточную нагрузку на Кассандру и использовать ее только для определенных таблиц в определенный период.
Что же, спустя полгода жизни, с Кассандрой? В целом, нерешенных проблем нет. Серьезных аварий и потери данных мы тоже не допустили. Да, пришлось подумать над компенсацией некоторых ранее не возникавших проблем, но в итоге это не сильно омрачило наше архитектурное решение. Если вы хотите и не боитесь пробовать что-то новое, и при этом не хотите сильно разочароваться, то приготовьтесь к тому, что бесплатного ничего не бывает. Разбираться, вникать в документацию и собирать свои индивидуальные грабли придётся больше, чем в старом legacy решении и никакая теория не подскажет заранее, какие именно грабли ждут именно вас.
alkresin
А в чем, собственно, проблема со взглядом Кассандры? Может, вы перепутали ее с Горгоной?
limassolsk
Потому что:
wikipedia