Всем привет! Меня зовут Александр Павленко, PHP-разработчик в NIX и спикер NIXMultiConf. В IT-сфере я наблюдаю такую тенденцию: чем масштабнее проект и чем быстрее растет разработка, тем чаще команде приходится менять, расширять логику приложения и улучшать функционал. В крупных проектах постоянный рефакторинг — неизбежный процесс. Иногда за ним скрываются проблемы, но их не стоит бояться. Подобные моменты — отличный шанс получить новые скиллы, прокачать свою экспертизу и получить доверие клиента.
В этой статье я поделюсь опытом нашей команды и расскажу, как мы решили все проблемы. Я выделил то, что, на мой взгляд, требовало от нас наибольшей оперативности.
С чего все начиналось
Продукт, с которым мы имели дело — сайт по продаже автомобилей в США. Когда мы заходили в проект, планировался MVP. На тот момент 80% текущего функционала даже не предполагалось делать. Проект постоянно рос, менялся, добавлялись взаимодействия. Даже когда заказчик думал, что он перешел в саппорт, мы все еще внедряли новые фичи. Доходило того, что релизы были чуть ли не каждую неделю.
Одной из таких фундаментальных функций стал inventory — бэкенд-процесс, влияющий на актуальность информации, которую видит пользователь. При этом юзер о нем никогда не догадывался. Inventory отвечал за ежедневную обработку данных от дилеров — информации о машинах и их фотографии. Каждый день на сайте обновлялись и добавлялись новые данные в среднем о 5 млн машин. И этот процесс нуждался в быстрой реализации.
Сначала у заказчика не было четкого видения, что он хочет получить в итоге. Впоследствии все происходило в спешке. Некоторые неточности привели к тому, что обработка данных занимала 14 часов, не было адекватного логирования о том, какие шаги мы прошли, и если что-то упало, то где и почему. Также наблюдалась высокая частота отказов (например, с появлением некорректного файла). Проблема была даже не столько в количестве отказов, сколько в том, что каждый раз их надо было запускать заново вручную и следить за каждым шагом.
Все это в совокупности приводило к большой вероятности потери данных. Если процесс ломался ночью, а логирования как такового не было, на следующее утро при ручном перезапуске система игнорировала новый файл. Соответственно — терялись новые данные. Если оставить все как есть — вчерашняя информация тоже никак не менялась. В итоге пользователи видели неактуальные данные. Страдал заказчик, страдали мы.
Чтобы решить проблему, мы пошли путем распараллеливания процессов и свели ручное управление к минимуму. Сейчас весь процесс занимает пару часов. Поскольку он стартует примерно за два часа до бизнес-времени заказчика, то сайт успевает полноценно обновиться. А если и случались заминки, то его можно было быстро перезапустить одной командой.
Индексирование. Опыт первых «граблей»
Как Crawler видел наш сайт? Да никак. Страницы сайта либо не индексировались, либо индексировались некорректно. Это влияло на показатели и поисковые запросы и на настроение заказчика в целом.Наше SPA-приложение (Single page application) было реализовано на React. Последние лет пять вопрос SEO для таких приложений очень актуален. Посерчив проблему в интернете, мы наткнулись на два совершенно полярных мнения: одни считают, что никаких проблем с индексацией SPA-приложений нет, другие говорят, что в той или иной мере есть, и они даже уступают монолиту.
На старте проекта в 2017 году популярные поисковики обычно частично или полностью не поддерживали индексирование JS-приложений, за исключением Google. Мы прикрутили Prerender, который должен был решить все наши проблемы, и в каких-то местах показатели улучшились. Но радоваться было рано.
На фоне проблем с индексацией возникла еще одна трудность — большой объем JS и сторонних JS-скриптов, с которыми мы интегрировались. Их количество только росло, поскольку это была инициатива заказчика. Мы понимали, что в какой-то момент это повлияет на производительность. Так мы подошли к радикальным изменениям — переписали основные страницы сайта на монолит (основную страницу поискового запроса и страницу автомобилей). Они были самыми часто запрашиваемыми и на них была самая большая нагрузка по JS. Позитивные изменения не заставили себя долго ждать.
Для нас это стало хорошим уроком: всегда глубже инвестигейтить любые вопросы и подталкивать к этому заказчика. Для последующих изменений важно понимать, что клиент ждет от разработки.
Большие объемы данных и их фильтрация
Достаточно быстро мы вышли на 15 млн пользователей (спойлер: в процессе работы дошли до 40 млн). Учитывая опыт первых «граблей», мы понимали, что надо изначально закладывать такую логику, чтобы не было проблем с обработкой данных. Так мы смогли бы в полной мере реализовать поставленный перед нами функционал и делать это эффективно.
В поисках решения искали среди крупных компаний максимально приближенные к нашим таскам примеры. Stackoverflow, Netflix, SoundCloud, GitHub и другие ресурсы в той или иной степени использовали два инструмента — Elasticsearch и Redis. В итоге и мы выбрали их для своего проекта.
Что требовалось сделать? Речь шла о хранении большого объема записей. У сайта были различные дилерские программы для пользователей, и с ними тоже надо было считаться. Они участвовали в поиске и в фильтрации данных. Также требовалось обеспечить возможность сохранять понравившееся машины и поисковые запросы. Это личная боль заказчика. Ему не нравилось, что можно было, проведя на сайте какое-то время, используя множество фильтров и сортировку для поиска, вмиг это потерять.
Перед нами — более 20 видов взаимосвязанных фильтров. Выбор одного непосредственно влиял на возможности других. Сортировки были со сложными условиями по множеству показателей. Например, одна из последних сортировок представляла собой многоступенчатые условия и влияла на уровень приоритетности того или иного авто в списке.
Обратившись к Elasticsearch и Redis, мы сразу разграничили их зоны влияния. Elasticsearch стал неким хранилищем основной информации и помогал с обработкой определенных данных и фильтрацией поиска. Redis сначала отвели роль небольшого хранилища кешей, которое потом разрослось и по вариативности, и по функционалу. Благодаря этим инструментам мы обошли «грабли», которые у нас могли произойти с данными. Новые трудности были функциональными и больше касались изменений на ходу от клиента. С ними мы справились довольно быстро и эффективно.
Борьба за прибыльный и качественный продукт
Каждый заказчик хочет, чтобы у него был самый большой, продуктивный, узнаваемый и, самое главное, прибыльный продукт. Наш клиент — не исключение. Он постоянно искал способы улучшить ресурс. Тематика сайта по продаже автомобилей очень популярна. Есть множество аналогов и, соответственно, конкурентов. Среди них нам требовалось выделить двоих, с которыми наш заказчик вел негласную конкуренцию. В чем-то он пытался равняться на них, в чем-то — обойти.
В процессе работы мы сравнивали ресурсы по двум показателям:
визуальная загрузка страницы — когда подъехал контент, который видит пользователь, и он может с ним что-то делать; полная загрузка — когда к визуальному контенту подгрузились все скрипты и процессы. Из-за большого количества интеграций и некоторых недочетов с нашей стороны мы сильно уступали конкурентам по второму показателю. В том числе очень огорчала низкая производительность страницы поиска. Требовалась срочная оптимизация. Таким образом мы бы превзошли или хотя бы вышли на один уровень с конкурентами и стали бы более удобными для пользователей. Здесь важно отметить, что в какой-то момент клиент стал активно предлагать нововведения.
Многое мы меняли и добавляли в спешке. Конечная бизнес-логика сильно отличалась от первоначальной. Сроки горели.
Из-за нехватки времени мы не достаточно детально рассмотрели особенности новой бизнес-логики. Было несколько недочетов с нашей стороны. Это в том числе стало причиной оптимизации определенных частей продукта.
Среди всех оптимизаций хочу выделить кеширование. Процесс разделили на два этапа:
за кеширование на Nginx отвечали наши админы;
часть PHP лежала на плечах девелоперов.
Проанализировав сущности и структуры, мы определили моменты, которые можем закешировать отдельно. Более того — выяснили, какие сущности меняются чаще/реже всего, какую информацию они несут и насколько важны для юзеров. Поэтому мы не просто по отдельности их кешировали, но и выбирали разное время «жизни» кеша, а также дополнительные процессы, которые могли в определенных условиях внепланово пересоздавать. Например, вероятность того, что появятся новые фильтры была намного меньше, чем то, что дилер пришлет новую машину, которая полностью соответствует текущим фильтрам (бренд, модель). В этом случае жизнеспособность кеша была меньше. Так мы минимизировали возможный прокол с актуальностью данных.
После улучшения кеширования вместе с другими оптимизациями мы увидели положительный результат в работе всего сайта.
Статистика говорила сама за себя: в визуальной загрузке мы обошли соперников, а в полной — вплотную приблизились к первому конкуренту, что тоже можно считать успехом.
Результат оптимизации — погоня за перформансом
Мне очень нравится фраза американского математика, автора бестселлера «Искусство программирования» Дональда Кнута:
«Если оптимизировать все, что можно, вы вечно будете несчастным»
Но клиенту настолько понравился результат оптимизации, что он нацелился на максимально быстрый перфоманс. Нашел несколько тулзов для его измерения и бросил львиную долю наших сил на анализ этих инструментов.
Мы испробовали несколько. Например, Dareboost и Google Audits. Первый ресурс был полноценным сайтом с множеством метрик и позволял покадрово смотреть загрузку страницы вплоть до миллисекунд, второй — это обычная вкладка в инспекторе Chrome, которая делала то же самое, но менее детально.
В Google Audits можно выбрать тип приложения для анализа (мобильное или десктопное) и показатели для проверки сайт.
Уязвимости ресурс делит на пять категорий:
accessibility;
performance;
progressive web app;
best practice;
SEO.
Наш продукт не относился к PWA, поэтому мы проверяли его по остальным категориям.
Весь функционал Dareboost доступен по платной подписке. Ресурс даже умеет определять, какие технологии использовались для создания страницы. По результатам анализа можно строить графики и выявлять моменты, которые следовало бы улучшить в будущем: то, что можно исправить, хотя это не несет чего-то критического, и проблемы, требующие срочного решения.
По отношению к нашему сайту Dareboost и Google Audits сошлись в советах. Чаще всего они отмечали нехватку мета-тегов и http-заголовков. Или, по мнению тулзов, они были настроены не достаточно секьюрно.
Постепенно наша погоня за перфомансом свелась к тому, что мы улучшили кеширование, добавили на страницы http-заголовки и мета-теги и сделали мобильную версию user-friendly. Все изменения прошли в кратчайшие сроки. Остальное время мы пытались объяснить заказчику, что любые последующие изменения тратят его и наши ресурсы, нежели дают видимый эффект. Нужно остановиться и понять, если ли в этом смысл.
Доходило до абсурда: Google Audits говорил, что у нас не было корпоративного цвета в мета-теге, который влиял на цвет url при поисковом запросе. Но с его добавлением визуально ничего не менялось, но время на этот таск мы уже потратили.
Как мы дошли до 50 часов работы одного специалиста
Долгое время этот момент никого в команде не беспокоил. Да и проблем с этим не было. Но стандартное обновление Elastic и Laravel привело к тому, что библиотеки, которые мы использовали, вообще перестали поддерживаться. Пришлось во многих местах собственноручно что-то дописывать или создавать какие-то прослойки. Несложно, но забирает массу времени. В нашем случае штатное обновление вылилось почти в 50 часов работы одного специалиста.
Мы задумались: не написать ли нам скрипты, которые следили бы за библиотеками? В тех же мажорных версиях Laravel пишут про обновления и исправления секьюрных уязвимостей, что очень важно. Думаю, ни один заказчик не откажется выделить время, чтобы решить вопросы безопасности. Как только мы взяли этот момент под контроль, процесс пошел легче. Следующие апдейты были регулярными и занимали минимальное время.
Что хотелось бы сказать напоследок: подобные и другие «грабли», на которые вы натыкаетесь — это неотъемлемая часть жизни большого проекта. Не нужно из-за них расстраиваться, искать виновного или каждый раз пугаться перед очередными трудностями. Просто беритесь за них, подымайте систему и повышайте клиенту настроение. Разобравшись в том, чего сами раньше не знали, или объяснив что-то новое коллеге, в будущем вы будете намного быстрее справляться с задачами «со звездочкой».