Меня пугают большие амбициозные цели. Я могу согласиться пару раз в неделю ходить в спортзал, но вряд ли смогу когда-нибудь «подтянуть мышечный каркас» или «стать здоровым/накачанным». Со второй целью, всякий раз, когда я пропущу спортзал (а такое рано или поздно случится даже если я — биоробот), я начну думать: «а накачаю ли я теперь мышцы? А не потерял ли я в результате?». Это тяжело.
Имея простую нестрашную цель «ходить в зал несколько раз в неделю», я пропускаю спортзал гораздо спокойнее — никто же не умрёт, если я на этой неделе схожу не три раза, а два или даже один раз?
Так работает со всеми большими целями: я никогда не решусь похудеть на 10 килограммов, но вполне смогу большую часть недели потреблять не больше 1700 килокалорий в день. Вряд ли я когда-нибудь решился бы построить бизнес — это страшно. Зато мне вполне хватило смелости уволится с работы и попробовать что-нибудь поделать в течение пары месяцев.
Может и вам поможет? Если часто не достигаете больших целей, попробуйте ставить маленькие цели на процесс, а не большие цели на результат, мне помогает.
Когда мне выпадает возможность взять новую и сложную активность вроде нового клиента или нового проекта, я пытаюсь понять: насколько я вообще хочу этим заниматься.
В какой-то момент таких активностей стало настолько много, что я в них погряз — просто брал всё, что казалось интересным и перегружался. В итоге не делал ничего важного, фокусируясь только на срочных делах, не делать которые было невозможно.
Сейчас, каждый раз, когда нужно принять решение, я провожу простой мысленный эксперимент: «Готов ли я этим заниматься всю жизнь?».
— Готов ли я всю жизнь писать код для клиента, чья доменная область у меня вызывает отвращение? Ну тогда и начинать не стоит.
— Готов ли я всю жизнь делать проект, который не принесёт денег или морального удовлетворения? Нет.
— Готов ли я всю жизнь вести бизнес, который никогда не смасштабируется? Вряд ли.
Возможно метод работает потому, что вся жизнь — это настолько серьёзный коммитмент, что мозг выключается из ежедневной тупой рутины и начинает думать глобальнее. Может ещё почему-нибудь, не знаю. Но пустых и неважных проектов у меня стало гораздо меньше.
У меня недавно случилось озарение — я понял почему мне так знакомо чувство, которое возникает, когда я работаю с модулями nuxt.js. Вот взять модуль gtm, к примеру. Среди кучи других issues у него есть issue, которая так честно и называется — «модуль не работает». И что интересно — модуль и правда не работает.
Так вот, когда-то давно, лет 15 назад, у меня была Нива — это такие жигули-кроссовер. И меняли мне на ней как-то стартер. Там эта операция проводится довольно сложно — стартер расположен крайне неудобно, приходится даже разбирать выхлопную систему. Так вот, купил я стартер, поставили его мне, собрали всё, а машина не заводится: стартер не крутит. Полезли разбираться, и выяснили, что проблема в том, что я купил рязанский стартер (не уверен насчёт города, может белгородский, или тамбовский).
Оказалось, что я не владел тайным знанием, которое на тот момент было у любого владельца жигулей — «рязанские стартеры не работают». Вот и купил по глупости.
В итоге стартер я купил белорусский, машина завелась. Но этот вопрос меня ещё долго не отпускал — ведь у этого завода в Рязани есть директор, главный инженер, руководитель ОТК, и ещё куча людей. Зачем все они делают стартеры, которые не работают?
Вот так же и с модулями накста. Ну вот реально чуваки постоянно что-то релизят, последний релиз в ноябре был. Какие-то фичи там даже добавили. Но вот зачем они делают релизы, которые не работают, а?
Недавно мы закончили важную веху — запустили новый движок блогов на snob.ru. Задача была нетривиальной — за полгода мы перезапустили сайт высоконагруженного медиа с кучей легаси-кода. В этой заметке я расскажу, какие технологические решения мы приняли.
Задача
Сноб — это интернет-медиа, статьи в котором пишут не только штатные редакторы, но и внешние участники проекта: любой человек может приобрести подписку и завести собственную колонку на snob.ru. Коду проекта больше 10 лет, писали его разные люди на совершенно разных технологиях — в копилке есть и Zend Framework с MySQL и Django с PostgreSQL.
Нас с Саматом позвали, когда разработка была в плачевном состоянии: новые фичи уже не выкатывали, а починка одного бага приводила к появлению двух-трёх новых. Первым делом мы провели аудит: поговорили с представителями бизнеса и программистами, расковыряли исходный код и инфраструктуру. Проблемы оказались буквально везде: программисты были уставшими, инфраструктура — непрозрачной, а техдолг — огромным. Остановимся на техдолге чуть-чуть подробнее.
Проект состоит не из одного бэкенда, а из трёх: основной сайт, блоги внешних участников проекта и редакционная админка. Архитектуру взаимодействия никто не продумывал — каждую новую систему городили на предыдущие как придётся. Это привело к тому, что данные между бекендами стали передаваться совершенно непредсказуемым образом: частично через запись в базу, частично — через вебхуки. Из-за этого пользователи периодически теряют данные: если материал, профиль или комментарий не укладывается в формат обмена между системами (или в момент сохранения пролетает птичка и моргает сеть) — данные портятся.
Кроме трёх бэкендов, у проекта есть ещё три фронтенда: старый от ПХП-движка, куча кода на Django и SPA на next.js, от которого предыдущая команда успела внедрить совсем небольшие части функциональности. Сверху всего этого стоит nginx, который одним ему ведомым образом решает, какая из этих систем будет отрабатывать запрос.
Поговорив с бизнесом, мы поняли, что самая большая проблема — в ПХП-движке на Zend Framework, который обслуживает блоги участников проекта. У бизнеса есть куча гипотез, которые можно проверить, но ни одного ПХП-шинка в команде не осталось, а внешних нанять невозможно — ни один нормальный программист не пойдет работать на 10-летний легаси без здоровой инженерной культуры.
Решение
Конечно, работать дальше с таким легаси нельзя — надо как можно скорее от него избавляться. Поскольку бизнес больше всего хотел решить проблему с блогами — с них мы и начнём. Мы поставили амбициозную цель — в конце работы оставить движок, которым пользуются и блогеры, и редакция: такое уже есть у Комитета, на их «Основе» работают все сайты издательства: vc, tjournal, dtf, и редакция там пишет посты так же, как и обычные пользователи.
Архитектура: однонаправленный поток данных
Решение мы начали с разработки архитектуры. В реальном мире существует только одно состояние у поста, комментария или пользователя — то, которое мы видим на экране. Проблема старой архитектуры в том, из-за ошибок проектирования это состояние в разных версиях размазано между совершенно несвязанными базами данных, и записывают его неаккуратные и несогласованные друг с другом системы. Представьте себе текст на листе бумаги, который одновременно пишут четыре первоклассника. Даже если они договорятся писать по одному слову за раз и вместе напишут связанный текст — вы никогда не поймёте, кто из них пропустил запятую или допустил смысловую ошибку.
Чтобы всегда знать, кто, зачем и когда записал данные, мы ввели единый источник правды. Пусть правдивое состояние пользователей и постов всегда находится у нас, и мы сами отвечаем за то, чтобы данные обновились в легаси-системах — транслируем все изменения в базы данных, задействуя как можно меньше старого кода. Всю синхронизацию систем друг с другом мы отрубаем — данные везде пишем только мы. Получается, что правда течёт сверху вниз, как в компонентах react.js — от нашей системы к легаси.
Экспорт данных мы построили на celery и RabbitMQ. Получилась полноценная асинхронная архитектура: все посты, которые нужно отправить в легаси, лежат в RabbitMQ, и удаляются оттуда только после того, как данные попадают во все БД. Если с трансляцией что-то пойдёт не так — мы узнаем об этом по переполненной очереди в RabbitMQ.
Инфраструктура
Инфраструктура на проекте — ещё один источник проблем. Там были разные физические серверы, конфигурация которых мутировала в течении десятилетия. ПХП-движок вообще крутился на FreeBSD — такой привет из начала 2000х! Плюс, у нас не было доступа к серверам — нельзя было даже зайти по ssh и посмотреть, что происходит.
Конечно, мы совсем не хотели делать ещё одну систему в этой непрозрачной мешанине — пара недель ушла бы только на попытки разобраться в конфигурации nginx. Решение пришло из мира фронтенда: там часто делают отдельный бекенд для фронтендеров, который который облегчает хождение в основные бекенды — маршрутизирует запросы между микросервисами, переформатирует ответы в удобный фронтенду формат, сохраняет данные авторизации — это называется BFF (Backend for Frontend). В нашем проекте уже был свой BFF — ведь нам нужно рендерить страницы на сервере, чтобы ускорить загрузку и быть понятными для поисковых роботов. Нам ничего не мешает маршрутизировать весь трафик snob.ru, включая статику через свой BFF — таким образом мы заберём полный контроль над трафиком.
Рядом с легаси-инфраструктурой мы развернули свою собственную, где на входе пользователей встречает комбинация из traefik и express.js. Теперь мы сами решаем, какая из систем обрабатывает каждый запрос — каждый новый сервис сам регистрируется в traefik и получает свою долю трафика. Если ни один сервис не хочет обрабатывать запрос — он уходит в express.js, где мы кодом решаем, обработать сервис своим фронтендом, или отдать его в легаси.
Чтобы не надорваться от нагрузки, поверх подключили Cloudflare, который раздаёт статику через свой CDN:
Заодно мы решили ещё одну проблему старой инфраструктуры — пропуская через себя весь трафик, мы наконец-то получили нормальную статистику происходящего в ней. Старый мониторинг строился на основе простой пинговалки в заббиксе: раз в минуту ходим в бекенд, если ответ не ок — шлём СМС админу. Чтобы понять, насколько это плохо — представьте ситуацию, в которой сайт падает от нагрузки и нормально обрабатывает только 50% пользователей. Если пинговалка попадает в ту половину пользователей, для которых сайт работает — админ об этом никогда не узнает. Наверное так же можно проверять работу ядерного реактора — если в контейнменте ничего не горит и не взрывается, значит реактор работает.
Теперь никакой пинговалки нет, а мониторинг строится на 4-х золотых сигналах — количестве запросов, времени ответа, количестве ошибок и запасу на оборудовании.
Если хоть какой-то параметр выходит из строя, робот на основе машинного обучения шлёт нам алёрт.
Как мы тестировали легаси
Закрытая инфраструктура создала нам ещё одну проблему — мы не могли собрать тестовые стенды. В старом коде в неподдающемся учёту количестве мест были захардкожены адреса и пароли продакшена — то есть даже если развернуть систему на машине разработчика, никто не может быть уверен, что локальный экземпляр не запишет что-нибудь в боевую базу.
Получается, что как ни пиши автотесты в нашем коде, мы не можем быть уверены в качестве системы в целом — не существует никакого способа проверить, что когда мы выкатимся на продакшен, ничего не упадёт.
Единственный подход к тестированию, который применим в данном случае — канареечный: когда мы выкатываем всю систему в прод, но показываем её минимальному количеству пользователей. Примерно за три месяца до запуска у нас в продакшене появилась рабочая система — через новый интерфейс можно было написать пост, который появился бы во сразу всех базах. При желании можно было даже вывести этот пост на главную страницу snob.ru! Конечно, система была непроработанной — сначала можно было написать только заголовок и текст, указать автора: ни о каком сложном форматировании речи не шло.
Новая система была в продакшене за два месяца до дедлайна — это дало нам достаточно времени, чтобы решить все возможные интеграционные проблемы.
Запуск
Несмотря на то, что к дедлайну система была уже в продакшене, оставались самые опасные вещи — переписать DNS и включить боевой стриминг данных реальных пользователей. Опасность была в том, что старая система в совершенно непредсказуемых местах ходила сама в себя (помните вебхуки?). В коде были конструкции вида urlopen('<http://snob.ru/secret_api/secret_endpoint>');! Большая часть этих адресов оставалась работоспособной — почти весь трафик мы перенаправляли в легаси, обрабатывая самостоятельно только нужную нам часть. Но какие-то легаси-адреса всё равно сломалась — к примеру перестал работать старый механизм загрузки фотографий.
Поскольку, не переписывая DNS, мы не могли этого проверить — оставалось тестировать всё на боевой системе. Для этого мы собрали процесс, который позволяет быстро переключить трафик со старого кода на новый и обратно. Чтобы включить новый прод, достаточно было поменять адрес в Cloudflare и раскатать плейбук Ansible с обновлёнными настройками — весь процесс занимал около трёх минут. Чтобы ничего не забыть, сделали простой чеклист в бейскемпе:
Всего мы сделали четыре тестовых запуска, все были в будни и без потери трафика. Не буду останавливаться подробно на всех проблемах, которые мы выловили в ходе тестирования — большая часть решалась тем, чтобы поправить очередную захардкоженную строку в легаси.
Что дальше
Сейчас у нас есть движок, в котором решены все проблемы интеграции, а код и основные пользовательские сценарии покрыты тестами. Остаётся потихоньку забирать функциональность у легаси-кода и тушить старые части системы, переводя весь snob.ru на новые рельсы. До встречи через полгода :-)
Команда
Никита Алёшников, бэкенд-разработчик Фёдор Борщёв, технический директор Михаил Бурмистров, ведущий фронтенд-разработчик Самат Галимов, технический директор Вячеслав Набатчиков, бэкенд-разработчик Всеволод Скрипник, бэкенд-разработчик, руководитель проекта Денис Сурков, бэкенд-разработчик Владимир Тарановский, фронтенд-разработчик
Наш дорогой заказчик:
Марина Геворкян, генеральный директор Валерия Тищенко, бренд-директор, продакт Артём Алексеев, дизайнер Мария Семенюк, директор по маркетингу Виктория Владимирова, директор по дистрибуции Борис Тавакалов, ведущий разработчик и хранитель знаний legacy-системы Михаил Лавкин, системный администратор legacy-системы Данияр Шекебаев, аналитик Александр Тарасов, техподдержка