Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В прошлом выпуске я рассказывал, зачем оплачивать покупки по QR-коду, и с какими трудностями столкнулась команда, когда мы встраивали новый тип оплаты в старую архитектуру. Сегодня постараюсь объяснить, как нам удалось провести подобное обновление на бэкенде без остановки работы сервиса.
Но перед тем, как погрузиться в технические детали, давайте рассмотрим, как у нас устроен процесс разработки: от получения задачи до релиза, чтобы понимать весь контекст. Расскажу, как это было применительно к оплате по QR.
Задачи появляются из владельца продукта — это может быть владелец продукта в конкретной команде, директор по продукту или генеральный директор. Я тоже могу, но в основном занимаюсь чисто техническими вопросами. Все эти задачи укладываются в бэклог, а на планировании попадают в спринт.
После того, как задача принята в работу, аналитики приступают к изучению документации. Они смотрят, как устроен API банка и как новый функционал повлияет на систему в целом: надо ли шатать базу, какие эндпоинты потребуется добавить, а где только изменить поля в ответе… В общем, хотят понять, что наш ждёт, и как сильно придётся растягивать сову, чтобы натянуть её на глобус (в данном случае мы сами чуть не лопнули). Те места, где документация протухла, или её никогда не было, помогают раскапывать разработчики.
Я уже слышал об СБП ранее, но только после того, как начал работать над этой задачей, стал немного понимать, о чём речь.
Когда аналитика готова, начинается активная разработка. Сами аналитики никуда не исчезают — они продолжают отвечать на вопросы и актуализируют документацию, если в процессе появляются новые идеи. Параллельно тестировщики занимаются написанием тест-планов. Работа кипит.
Через какое-то время появляется черновой вариант решения, которой не стыдно показать на демо. Код проходит ревью внутри команды, и когда все вопросы закрыты, мы начинаем подготовку к релизу. А точнее, к релизам.
Релизы без остановки
Обычно, когда надо сделать релиз, на сайт просто ставят заглушку, обновляют всё, что надо, и поднимают обратно. До недавних так делали и в МоёмСкладе. Ночью приходила команда инфраструктуры с парой дежурных программистов и проводила обновление. Сейчас всё немного по-другому. Мы обновляемся без остановки сервиса.
Принцип обновления без остановки можно объяснить на пальцах:
Если надо, проводим миграцию базы.
Поднимаем обновлённые копии сервисов рядом со старыми.
Переключаем пользователей на копии, старые версии тушим.
Клиент, который открыл МойСклад во время второго этапа, сразу попадает на новую версию. Для тех, кто в этот момент занимался своими делами, всплывёт предупреждение, что через 15 минут страница перезагрузится (тут мы следуем лучшим практикам Windows).
Такая схема требует требует более аккуратного подхода к миграции. Тем более, что у нас 15ТБ данных, которые разложены по нескольким десяткам серверов. Я решил взять комментарий у Ильи Коляскина, разработчика, который делал такую миграцию для оплаты по QR. Он согласился рассказать о своей работе для нашего блога.
Илья о сложностях в базе
Так как обновление базы и непосредственно релиз происходят в разное время (сначала обновляется база, затем выкатываются все сервисы), между этими событиями есть небольшой интервал времени, в который приложение работает с обновлённой базой, но со старыми сервисами. Получается, что за один релиз перенести данные из одних полей в БД в другие простым скриптом не получится.
Для этого релиз разбивается на три этапа:
Сначала создаются новые поля, пока пустые, куда позже будут перенесены данные. В коде сервисов выпускается фикс, который при создании пользователем новых записей заполняет в БД и старые поля, и новые.
Теперь у нас есть гарантия, что новые созданные записи заполнены корректно. Можно переносить данные из старых полей в новые и выпускать фичу. В коде использование старых полей вычищается. Поскольку старые версии сервисов ещё активны, дропать поля нельзя.
Старые поля, которые уже не используются в коде, удаляются из БД.
Из трудностей, с которыми столкнулись:
Долгое время апдейта одной пачки из-за большого количество индексов, которые сильно замедляют апдейт. Индексы убирать нельзя, так как без них пользователь не сможет построить ни один отчет.
Для ускорения всего обновления не стали использовать vacuum между пачками, что в итоге вылилось в масштабное снижение доступного места на дисках БД, которое пришлось решать позже переупаковкой таблицы.
Чтобы провести второй этап обновления, не мешая работе пользователей, оно производилось во время наименьшей нагрузки — поздним вечером и ночью: записи обновлялись небольшими пачками параллельно с работой системы.
Итого по времени весь апдейт занял примерно 2 ночи.
А теперь обратно к процессу
Поскольку обновление сервиса становится нетривиальным, релиз, разбитый на несколько миграций, проходит стандартное ревью внутри команды и ревью в команде архитекторов. Для обычных задач мы просто смотрим код друг друга и сразу отправляем в тестирование. Тут задача был со всех сторон сложной, поэтому тест-планы и аналитика тоже проходили ревью у лидеров данных компетенций.
Каждый релиз тестируется на предмет обратной совместимости не только с UI основного сервиса, но и с кассовым ПО на всех платформах, чтобы исключить вариант, когда Касса работает на десктопе, но не работает на андроиде и айосе или наоборот.
По мере того, как выполняется тестирование, релизы укладываются в релизный поезд — в МоёмСкладе 10 команд, которые постоянно работают с основным сервисом, поэтому нужно соблюдать очередь. Выпускать большую задачу одновременно с другими опасно. Разруливанием рисков занимаются релиз-менеджеры. Если говорить об оплате QR, то мы проводили релизы на протяжении всего месяца.
Фактически же эта функция заработала немного раньше. Последний релиз был техническим, чтобы удалить лишние поля. Кассу мы выпустили на следующий день после того, как основные правки стали доступны на сервере. Но о том, как устроены релизы Кассы МойСклад, я расскажу в следующий раз!