Пару лет назад мы располагались в самом cost-effective (читай: «дешевом») дата-центре в Германии. Чтобы вы понимали условия — роутинг мог сбоить от стойки к стойке или внутри неё; свитч в стойке перегружался или зависал; сам дата-центр постоянно ддосили; жесткие диски выходили из строя; материнские платы и сетевые карты сгорали; сервера произвольно выключались, перезагружались, а сетевые кабели выпадали как осенние листья во время урагана.
Периодически, когда приходило время скейлиться горизонтально, в ДЦ еще и место заканчивалось, и нам предлагали другую локацию, в другом городе, что для наших условий (ограничения схемы данных, топология кластера и критичность времени ожидания клиента) было неприемлемо.
Наступила точка кипения и мы решили переезжать. Хотя в какой-то момент даже казалось, что дешевле нанять больше обслуживающего персонала, чтобы менеджерить ситуации в вышеупомянутом ДЦ. Но в итоге, чтобы «стало более лучше жить» — мы выбрали стабильность.
Выбор остановился на дата-центре в Голландии, в Амстердаме. А вот тут самое интересное: к тому времени у игры уже был приличный DAU, переезд нужно было осуществить онлайн, без даунтаймов, одновременно на обе платформы (Android и iOS). Мало того, мы получили фичеринг на Google Play, маркетинг еще и запустил рекламную кампанию. Как понимаете, дополнительного трафика стало очень и очень много.
В общем, задачка не самая обыденная и вот как мы с ней справлялись.
Общая архитектура
Первый мобильный синхронный PvP-шутер для компании, которая занималась преимущественно казуальными играми для соцсетей — это как шаг в неизвестность. О том, как эволюционировала архитектура проекта War Robots в самом начале, наши ребята уже написали, ну а у нас — команды разработки и системных администраторов — были свои челенджи.
Итак, наша общая архитектура:
По порядку:
- Фронтенд-Бэкенд. Frontend: nginx; Backend: apiserver (tomcat) + hazelcast.
- Отказоустойчивость. Мы могли (до переезда) и можем сейчас потерять только две ноды кассандры из кластера без последствий. Имели от 20 до 30 нод на платформу.
- Репликейшен фактор. RF = 5.
Цели переезда
Во-первых, мы хотели сменить ДЦ, чтобы сделать игру более качественной. Доступность и отзывчивость сервисов — это один из самых важных компонентов надежности. Ну а во-вторых, пора было обновлять железо.
А вот достичь второстепенной цели — переехать на свежую кассандру — к сожалению, так и не удалось. Версии 2.1.13 и 2.1.15 в тестовых условиях не хотели нормально общаться друг с другом. Проводилось расследование, попытки понять почему, но сроки поджимали, поэтому оставили версию как есть. Собственно, поэтому на более свежие версии кассандры вообще не замахивались.
С чем столкнулись
Так как этап подготовки и тестирования затянулся и мы попали под увеличенную нагрузку (фичеринг со стороны Google Play и запуск маркетинговой кампании), поэтому необходимо было обеспечить супернадежность операции.
Рост DAU во время переезда в новый ДЦ с одновременным проведением рекламных кампаний и фичерингом.
По своей сути переезд — это сначала добавление второго ДЦ в кластер, переключение трафика, а затем удаление первого. Мы перевозили не только код, но и все данные игроков. Расстояние от приложения до БД должно быть минимальным. Для консистентности нужно постоянно писать в оба кольца, выбрав оптимальный consistency level, что накладывает на время ответа транспортные расходы на передачу данных в удаленный ДЦ. Нужно было обеспечить хорошую скорость доступа к БД в новом ДЦ (широкий и надежный канал связи между ДЦ).
Старый ДЦ имел весьма стабильные проблемы с железом и сетью. RF должен был спасти от внезапного выхода из строя железа. С возможностью развала кластера из-за сети можно было бороться тем, что мониторить процесс перегона данных, и в случае чего перекачивать данные несколько раз.
Что касается выбора нового дата-центра, то, вкратце, он должен был быть стабильнее в плане внутренней сети и связи с внешним миром и надежнее в плане железа. Мы готовы были платить больше за лучшее качество и настоящую, а не мнимую, доступность серверов 24/7.
Подготовка к переезду
Для этого нам нужно было сделать довольно много, а именно:
- Были сервера, на которых одновременно крутился и код и ноды Cassandra. Нужно было разгрузить сервера, где крутились одновременно бэкенд и ноды кассандры, оставить только томкаты на тех машинах, декоммишнуть ноды кассандры там, и вместо них развернуть ноды на отдельных машинах (цель: в случае нагрузок на кассандру в ходе переезда и наплыва игроков может подскочить использование ресурсов, нужно отдать БД все, что есть на машине).
- К сожалению, так сложилось, что старое кольцо работало на SimpleSnitch. Он не гибок, не позволяет использовать несколько DC в полной мере (в терминах кассандры). Для продуктива со своим железом рекомендуется GossipingPropertyFIleSnitch. Он позволяет динамически управлять кольцом через настройки и протокол gossip-ера. Сменить snitch (SimpleSnitch -> GossipingPropertyFileSnitch) и topology (SimpleStrategy -> NetworkTopologyStrategy). Инструкции по добавлению ДЦ и смене конфигурации БД не изобретались, все уже давно есть в документации.
- Протестировать переезд, т.к. до этого ничего такого не делали. Для этого взяли отдельно сервера, написали код, который создает нагрузку, и во время нагрузки меняли настройки, добавляли ДЦ в кассандру, перекачивали данные, включали нагрузку на новый ДЦ и проверяли данные в новом кольце. На этом этапе мы пробовали обновлять кассандру, но после обновления кольца переставали видеть друг друга и плевались ошибками. Причем пробовалось обновить оба кольца. Засада была где-то в этой версии кассандры. Также тут мы пробовали ломать связь между кольцами и наблюдать, как поведет себя кластер и ребилд. После этого отдельный код проверял, что мы затянули все данные.
- Подумать над вариантами отката и запасными вариантами, когда наступает стадия невозврата.
- Взять свежее и мощное железо, т.к. планировалось увеличение нагрузки (тут же, кстати, под кассандры поставили SSD). Нужно было посчитать объемы данных и взять диски с запасом, т.к. кассандра требует X2 и более места для ребилда. Все зависит от выбранной стратегии компакшна. Всегда нужно считать, чтобы было не меньше, чем X2.
- Подготовить настройки для новых машин. Одновременно с переездом в другой ДЦ мы решили переехать с Puppet на Ansible. В рамках этого же пункта мы планировали навести порядок в инфраструктуре, отточить конфигурирование серверов и быстрое добавление нового железа в строй. Также нужно было улучшить мониторинг.
Как надо было сделать
*DC1 — старый ДЦ, DC2 — новый ДЦ.
- Развернуть бэкенд в DC2 в отключенном видe, с write CL=EACH_QUORUM. Почему EACH_QUORUM? Если придется возвращаться в DC1, то так меньше шанс, что потеряются данные, т.к. они попадут в оба ДЦ.
- Развернуть пустые кассандры проставить настройки:
auto_bootstrap = false
seeds = пока что список сидов из старого ДЦ
endpoint_snitch = GossipingPropertyFileSnitch
dc = DC2 - Запустить кольцо нового ДЦ, проверить, что nodetool status в обоих ДЦ показывает, что все хорошо: все ноды подняты (UN), в логах БД ошибок нет.
- Настроить keyspace (объявляем, что данные будут в DC1, DC2):
UPDATE KEYSPACE WarRobots with placement_strategy = 'NetworkTopologyStrategy'
and strategy_options = {'dc1': 5, 'dc2': 5}; - Включить EACH_QUORUM на запись в бэкенде DC1.
- Запустить ребилд данных на DC2 с указанием, откуда брать данные для DC2:
nodetool rebuild dc1 - После окончания ребилда нужно запустить код в DC2, подключиться клиентами и протестировать.
- Последовательно под наблюдением перекинуть трафик nginx-ом на API в DC2.
- Понаблюдать и перекинуть DNS.
- Дождаться полного перевода трафика на DC2, выключить бэкенд на DC1, перевести бэкенд DC2 на writeCL=LOCAL_QUORUM (точка невозврата).
- Отключить DC1 от keyspace War Robots.
- Декомишнуть кольцо DC1.
Как сделали мы и почему
- Развернули бэкенд (в отключенном видe, с write CL=EACH_QUORUM).
- Развернули пустые кассандры, сконфигурировали.
- Запустили кольцо DC2.
- Настроили keyspace.
- Выполнили полный ребилд данных на DC2 с указанием источника DC1.
- Запустили бэкенд на DC2.
- Подключились клиентом, проверили, что все хорошо.
- Включили EACH_QUORUM на запись в бэкенде DC1.
- Запустили еще раз ребилд данных на DC2 с указанием брать данные из DC1. У нас были проблемы с сетью и железом в процессе переезда, поэтому провести второй ребилд посчитали необходимым.
- После окончания ребилда последовательно перекинули трафик nginx-ом на DC2.
- Понаблюдали и перекинули DNS.
- Дождались полного перевода трафика на DC2, выключили бэкенд на DC1, перевели бэкенд DC2 на writeCL=LOCAL_QUORUM (точка невозврата)
- Отключили DC1 от keyspace War Robots.
- Декомишнули кольцо DC1.
Риски при нашей реализации
Мы могли уйти в даунтайм из-за высокой нагрузки на базу на старом кольце (честно говоря, оно было не в лучшем состоянии). Мы также могли потенциально что-то потерять в ходе переноса данных в случае сбоев в сети — отчасти поэтому мы решили перестраховаться и провести два ребилда.
Положительные стороны
Мы могли переключиться назад в старый ДЦ в любой момент вплоть до точки невозврата. После точки невозврата тоже могли бы, но тогда бы мы потеряли прогресс по игрокам за несколько дней. Хотя мы могли бы его перелить обратно с DC2 в DC1.
Мы также имели возможность протестировать, что все хорошо с обоими DC кассандры и данные перетекают в обе стороны.
Какие моменты мы не рассчитали и как боролись с последствиями
В процессе переезда упала нода на DC1 (посыпался винт, закорраптились локальные данные ноды), пришлось ее вырубить и переезжать с одной запасной нодой в кольце.
Также для повторного ребилда у нас не хватило места для платформы iOS, пришлось оперативно докупать и расширять диски. Мы пробовали использовать сервера с HDD, чтобы размазать данные кольца DC2 и не докупать диски (покупка дисков — это время), но это катастрофически снизило скорость перегона данных. Поэтому пришлось все же дождаться поставки дисков.
Результат
В качестве результата наиболее наглядно будет привести графики (резкое падение на графиках связано с кратковременными проблемами с аналитикой, а не даунтайм, как может показаться):
Среднее время ответа на клиентский запрос загрузки профиля игрока.
Среднее время ответа БД.
К сожалению, показатели нагрузки железа не сохранились. По памяти, кассандры ели около 60% CPU в старом ДЦ. На данный момент значение держится на 20% (с тем, что DAU, в периоды высокого DAU и нагрузке может подниматься до 40%).
Итоги
Как ни странно (так как могло произойти что угодно, вплоть до классического перерубания основного кабеля дата-центра), мы переехали. На самом деле, в ходе операции мы ничего не изобрели. Все, в общем-то, написано в официальной документации и других открытых источниках. Однако со временем мы пересмотрели, что и как было сделано, и в целом можно сказать, что все сделано неплохо: разработан и протестирован план, качественно проведены работы, игровой мир в процессе не страдал, данные не потерялись. Конечно, после переезда еще многое дошлифовывалось и предстояло сделать, но это уже другая история.
На текущий же момент доступность и надежность игрового мира и данных игроков существенно выросла:
- мы ускорили работу сервисов за счет более мощного, надежного железа и переноса данных БД на SSD;
- снизили задержки в ответах клиенту за счет размещения серверов БД и кода как можно ближе друг к другу в пределах ДЦ;
- заменили систему управления инфраструктурой, усовершенствовали мониторинг;
- получили бесценный опыт по управлению кластером Cassandra и миграции данных в боевых условиях в другой дата-центр;
- ну и конечно же, привели в норму количество часов ночного сна у серверных разработчиков и системных администраторов.