Cassandra глазами Operations

в 13:17, , рубрики: cassandra, nosql, Блог компании «LifeStreet Media», метки:

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

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

Задача которую нужно было решать — каким образом хранить, искать, модифицировать информацию о последовательности событий при следующих условиях:

  • события могут происходить на разных серверах и в разных датацентрах (восточный и западный берег США, Европа)
  • интервал между событиями — от долей секунды до нескольких дней
  • к моменту получения завершающего события (например конверсия) информация обо всей цепочке должна быть на руках
  • время жизни информации — примерно десять дней, после чего она должна быть удалена, желательно автоматически, через TTL
  • темп чтения/записи событий — сотни или тысячи в секунду
  • Время ответа: желательное — до 10мс, допустимое — в пределах 50мс, максимальное — до 100мс
  • информация должна быть доступна «всегда» — независимо от аварий железа, сети, апгрейдов
  • система должна легко масштабироваться: добавление новых серверов, датацентров должно происходить прозрачно для остальных сервисов (допустима деградация времени ответа в заданных пределах).

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

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

Выбор железа

Самое большое опасение вызывало требование к рамкам времени ответа — на уровне 10мс. Второй параметр который нужно было соблюдать обязательно — наличие свободного места на диске для всяческих внутренних задач кассандры (compaction, repair,...). Третье — железо должно быть «простым» для того что-бы его можно было легко заменять при авариях и добавлять при расширении.

Времена ответа

Cassandra супер-быстра на запись. Обеспечивается это тем что данные попадают в таблицу в памяти, и в commit log, что означает: запись на диск всегда идёт последовательно, какие-либо существенные перемещения по диску отсутствуют ( поэтому commit log и таблицы, к которым происходит случайный доступ, рекомендуется держать на разных дисках).

Скорость чтения зависит от многих параметров и в конечном счете упирается в скорость случайного доступа к диску, если данные не умещаются целиком в памяти, и в скорость сети, если данные, необходимые для ответа на запрос, находятся на разных серверах. Среди методов борьбы с задержками ответа на чтение — увеличение RAM, использование компрессированных таблиц (появилось в версиях кассандры 1.0.х), тщательное планирование расположения и схемы данных, запросов к базе. Как видим — не всё здесь в руках operations, поэтому приходится планировать с некоторым запасом.

В результате прикидок и тестов решили остановиться на варианте 8 гиг RAM, 15000rpm SAS диск для хранения таблиц, и обычный 7200rpm SATA для commit log (на этом-же диске установлена операционка и сама Cassandra)

Выбор модели процессора не так важен, мы используем интеловские процессоры Q9400 @2.66GHz, X3470@2.93GHz.

Выбор обьема памяти в 8 гиг и дефолтных параметров кассандры для памяти JVM потребовал впоследствии подстроек, поскольку в некоторых ситуациях мы столкнулись с большими затратами на garbage collection в JVM.

На сегодняшний день времена ответа кассандры лежат почти всегда в пределах 5мс, увеличиваясь во время compaction до 30мс, и вылетая ненадолго до уровеня 100мс в случае сетевых проблем.

Свободное место на диске

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

Кассандре в обычном режиме работы (отсутствие compaction, добавление нового узла) нужно иметь запас как минимум 50% диска, потому что ближайший compaction может временно удвоить, а процесс repair в неудачных случаях и утроить потребляемое место. Поэтому мы взяли обьем диска с тройным запасом (у нас получились 300G) и установили уровни для disk usage в Nagios: warning — 30%, critical — 50%. До сегодняшнего дня практика подтверждает правильность расчетов, в обычной ситуации мы изредка вылетаем в warning и никогда — в critical. Во время repair занятое место возрастает до 50-60%, изредка до 70%.

Запуск в эксплуатацию

Пакеты

Перед запуском в эксплуатацию мы приготовили дебиановские пакеты, включающие в себя как саму кассандру, так и все необходимые локальные настройки, муниновские плагины, нагиосовские чеки. Ручная работа при запуске нового узла сведена к минимуму: мы вручную разбиваем диск на разделы и вручную назначаем initial token.

Кластер начинался с двух узлов в одном датацентре в на восточном берегу Америки, к которым постепенно добавлялись пары узлов в других датацентрах в единое кольцо. Решение добавлять узлы в кольцо без учёта географии было ошибочным, потому что как только пришлось подключить датацентр в Европе, стало видно что кросс-датацентровые задержки между США и Европой на уровне 100мс не позволят выдержать нужные времена ответа. К счастью Cassandra имеет механизм позволяющий управлять локализацией реплик по датацентрам — NetworkTopologyStrategy, на которую нам предстояло переключиться прямо под нагрузкой. После эксперимента на тестовом кольце стало понятно как сделать это безболезненно и с минимальным влиянием на работу системы. В течение нескольких минут непосредственно после переключения часть данных была недоступна для чтения, что на самом деле никто не заметил.

Датацентры

Сейчас наш кластер состоит из двенадцати узлов в шести датацентрах. Запуск нового датацентра состоит в заказе серверов нужной конфигурации, разметке диска, выборе нужных initial token для новых узлов и установке пакетов. Вся процедура запуска на новом сервере занимает до часа, с учетом времени начальной загрузки реплик данных (до 60G).

Мониторинг и документация

Еще на этапе экспериментирования с кассандрой были написаны муниновские плагины, которые давали нам всю необходимую информацию о узлах — времена ответа для каждого keyspace/columnfamily, количество операций чтения, записи, сжатия и т.д. стоящих в очереди, обьем данных на каждом узле и число таблиц на диске и т.д. и т.п. Наличие этих графиков очень важно для понимания того, что происходит в долгосрочном плане или что происходит во время каких-либо внутренних процедур типа repair, compaction.

Выяснение корреляции между разными параметрами позволяет понять как избежать увеличения времени ответа базы в трудные минуты жизни кластера.

Мы пробовали использовать OpsCenter от Datastax (даёт возможность видеть состояние не только в долгосрочном плане, но и в режиме realtime и управлять узлами из единого центра), но в силу определённых проблем с конфигурацией софта мы не смогли его использовать — пришлось написать собственный монитор сводящий текущие значения важнейших метрик в одно окно браузера. Пример вывода по двум серверам в обычном режиме работы в «ночное время»:

Node name Read lat,ms Write lat,ms Read, ops/s Write, ops/s Disk, % Java, % Data, G Streams, send G Streams, send % Streams, recv G Streams, recv %
Comp,

G

Comp,

%

node1 0.7 0.6 66.7 10.8 15.0 60.1 39.4 0.0 0.0 0.0 0.0 0.0 0.0
node2 0.7 0.6 66.5 10.3 15.0 20.7 39.7 0.0 0.0 0.0 0.0 0.0 0.0

те-же два сервера, но на втором идёт repair:

Node name Read lat,ms Write lat,ms Read, ops/s Write, ops/s Disk, % Java, % Data, G Streams, send G Streams, send % Streams, recv G Streams, recv %
Comp,

G

Comp,

%

node1 4.1 0.6 54.3 9.5 17.1 60.1 44.9 0.0 0.0 0.0 0.0 0.0 0.0
node2 2.6 0.5 54.5 9.5 18.5 55.2 48.9 1.9 22.3 0.0 0.0 2.5 1.2

Кроме того, мы задокументировали для себя основные процедуры либо в корпоративном wiki, либо в виде подробных комментариев по всем issue, связанным с кассандрой.

Набитые шишки

NTS

Ошибка, которая была допущена с самого начала — использование Simple Strategy вместо Network Topology Strategy, что привело к проблемам как только запустили узлы в удалённом датацентре. Исправлять ошибку пришлось на ходу, хотя ничего не мешало использовать NTS сразу. Теперь у нас репликация устроена так что мы держим по одной реплике данных в каждом датацентре. NetworkTopologyStrategy позволяет описать топологию кластера — в каком датацентре, в какой стойке находится тот или иной узел, после чего Cassandra оптимизирует доступ к данным. Любая запись или чтение обслуживаются локально и только в случае если нужный узел в данном датацентре недоступен, клиент ожидает данных из другого.

клиент, файловер

Наше приложение написано на java и мы используем библиотеку Hector для работы с базой. Hector устроен достаточно умно и предлагает клиенту разные политики выбора узла кластера для подключения. Первый вариант, на котором мы остановились — каждый клиент работал с тем узлом, от которого он получал самый быстрый ответ, казался интуитивно правильным, но реальность оказалась жестокой — получилось так, что если один из узлов отвечал заметно быстрее других, все клиенты бросались на него и фактически устраивали ему DDoS. Как только узел начинал отвечать медленне из-за повысившейся нагрузки все бросались на другого. Пришлось срочно переписать логику — сейчас клиенты ходят по всем «живым» узлам в своём датацентре по кругу — так что в пределах датацентра нагрузка на ноды распределена равномерно. Если узел не отвечает в течение 100мс, клиент отмечает его для себя как «мёртвый» и обращается к нему изредка для проверки его состояния. Это полностью решило нашу проблему.

Compactions, repair

Cassandra хранит данные на диске в виде отсортированных по ключу таблиц SSTable (вместе с несколькими вспомогательными файлами для каждой таблицы). Каждая новая SSTable образуется при сбросе на диск таблицы MemTable из памяти. Будучи раз записанной, SSTable никогда не изменяется. Единственный способ избавиться от старых SSTable и данных — процедура compaction, которая собирает несколько (или все) SSTable в одну. При этой сборке выполняется много важной работы — удаляются устаревшие tombstones(маркер удаленных данных), устаревшие по TTL данные помечаются удалёнными и tombstone для них помещается в новую SSTable. Некоторые виды compaction выполняются автоматически в фоне, некоторые могут инициироваться вручную. В результате такого сжатия появляются новые таблицы (как правило меньшим числом и обьемом), состоящие из смеси еще не устаревших tombstones и данных.

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

Операция repair выполняет синхронизацию данных и tombstones между репликами. Хотя Cassandra имеет средства синхронизации «на ходу» для случая если запись на одну из реплик не удалась с первого раза, все эти средства не дают стопроцентную гарантию. Поэтому для некоторых сценариев работы с данными периодически запускать repair — рекомендуется, а для некоторых — требуется. Поскольку в этот процесс может быть вовлечена передача довольно больших обьемов данных между узлами (и между датацентрами) важно минимизировать расхождения в данных непосредственно перед repair. Подбор оптимальной процедуры repair и подготовки к ней занял довольно много времени, поскольку операция тяжелая и слишком часто не поэкспериментируешь.

В результате у нас выработалась следующая стратегия запуска repair: процедура запускается в ночное время раз в неделю; предварительно для небольших ColumnFamily выполняется полное слияние таблиц на диске (этот процесс занимает мало ресурсов), и полное слияние «старых»(старше четырёх дней) таблиц для больших ColumnFamily. Последняя операция чистит много tombstones с одной стороны и потребляет относительно немного ресурсов — с другой. К моменту запуска repair на диске хранится минимум данных и процесс вычисления разницы и синхронизация между узлами соответственно занимает минимум времени — около часа.

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

Память и garbage collections

Еще один тонкий момент — использование памяти JVM. Особенно нагрузка на heap растёт во время параллельных compactions и repair. Дефолтные параметры могут не подойти для вас, придётся поэкспериментировать.

Рекомендации

  • Определите как можно раньше характер нагрузки и параметры, которые вам необходимо обеспечить для приложения. Требуйте от постановщика задачи подробных сведений. Проектируйте железо с учетом вашего анализа. Просите разработчиков тщательно проектировать хранение данных в базе.
  • Обеспечьте себе запас по месту и по скорости диска и по обьему RAM если для вас важны времена ответа.
  • Используйте одинаковое железо и софт на узлах, не плодите зоопарк.
  • Приготовьте детальный мониторинг всего. Неизвестно заранее какие данные понадобятся для анализа проблем.
  • Автоматизируйте установку новых узлов.
  • Разберитесь детально с жизненным циклом ваших данных в кассандре: как и где они сохраняются, как читаются, как уходят из базы.

Выводы

За последний год мы запустили три кластера из которых два работают в production-проектах, и один находится в стадии разработки. В целом можно сказать, что Cassandra — это рай для operations при условии внимательного отношения. За год эксплуатации у нас не было ни одной проблемы с кассандрой на отдельном узле или вылета кластера в целом, несмотря на нередкие аварии железа и замены/добавления отдельных серверов, регулярные апгрейды, непрерывные эксперименты на живых узлах с различными настройками и процессами. В конечном счёте и устойчивость, и масштабируемость её обеспечивается одним базовым принципом — все узлы играют одинаковую роль и связаны между собой не слишком жестко.

Автор: ik62

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js