Сезон распродаж — стресс-тест на прочность работоспособности сайта. При стандартной нагрузке «узких мест» почти незаметно, но как только запросов к каталогу, корзине, геосервисам и оформлению заказа становится значительно больше, начинаются трудности. В этом кейсе рассказывается, как команда агентства Lachestry (Лэчестри) подключилась к штатным разработчикам интернет-магазина и, «подхватив на лету процессы», усилила технологическую платформу e-commerce (электронной коммерции), обеспечив стабильную работу сайта под большой нагрузкой. Этим специалисты Lachestry (Лэчестри) не ограничились и реализовали новый интересный функционал.
О проекте
Заказчик проекта
Lime (Лайм) — популярный российский бренд модной дизайнерской одежды, обуви и аксессуаров, работающий на рынке с 2008 года.
Задача проекта
Весной прошлого года, чтобы подготовить платформу интернет-магазина к сезону распродаж, а также расширить функционал сайта, заказчик решил усилить собственное подразделение по поддержке и развитию платформы за счет внешних специалистов. Lime (Лайм) пригласили, т.к. нужно было погружаться в архитектуру, уже созданную другими разработчиками. Предстояло выявить источники нагрузки и заниматься последовательным развитием сайта.
Решение
Формат работы
Для реализации проекта была сформирована единая команда из специалистов Lime (Лайм) и Lachestry (Лэчестри). Эксперты Lachestry (Лэчестри) интегрировались в действующие процессы разработки и подключились к анализу архитектуры, проектированию решений, декомпозиции задач и техническому управлению PHP (ПХП) — направлением.
Новые компоненты системы не должны были становиться для команды «чёрным ящиком». Поэтому при разработке сохранили привычный для проекта технологический стек и единые подходы к эксплуатации.
С весны прошлого года Lachestry (Лэчестри) присоединился к развитию уже созданных:
- Laravel-монолита (Ларавел — монолитное приложение),
- payment-service (пэймент-сервис — сервис обработки платежей),
- receipt-service (рисит-сервис — сервис работы с чеками).
Описанные ниже Swoole-сервисы (Суол — сервисы на базе высокопроизводительного PHP (ПХП) — фреймворка и серверного движка) были спроектированы и разработаны командой Lachestry (Лэчестри).
Подход к архитектуре
На старте проекта значительная часть публичного API (Application Programming Interface — программный интерфейс приложения), каталожных операций и внешних интеграций была реализована внутри Laravel-монолита (Ларавел — PHP-фреймворк) и его операционной базы данных.
Полный переход на новую архитектуру потребовал бы масштабной миграции и существенно увеличил бы технические риски. Поэтому декомпозицию системы выполняли поэтапно — через последовательное выделение отдельных функциональных контуров в самостоятельные сервисы.
Основные принципы архитектурного подхода:
- выявляли наиболее нагруженные и при этом достаточно автономные функциональные контуры;
- выносили отдельные компоненты без изменения существующих публичных API-контрактов (АПИ);
- сохраняли PHP (ПХП) как основной язык разработки;
- постепенно переводили маршрутизацию запросов на новые компоненты, сохраняя исходную бизнес-логику в зоне ответственности её владельца;
- применяли fallback (фолбэк — резервный сценарий обработки запроса) только в тех участках, где механизм не создавал риска неконсистентного состояния данных.

Бизнес-фичи, которые добавлялись в систему, не собирались заканчиваться. Их становилось все больше. Оказалось, что не получится переписывать все с начала и останавливать бэклог.
Единая платформа для PHP (ПХП)-сервисов
Чтобы разработать новые высоконагруженные сервисы, в качестве технологической основы взяли Swoole (Суол — высокопроизводительное расширение для PHP) и Phalcon (Фалкон — PHP-фреймворк). Swoole обеспечивает работу с долгоживущими worker-процессами (воркер-процессами — постоянно работающими процессами обработки запросов) и coroutine-friendly I/O (корутинно-ориентированным вводом-выводом), а Phalcon отвечает за прикладной слой: маршрутизацию, dependency injection (ди — внедрение зависимостей) и модульную организацию приложения.
Общие инфраструктурные компоненты перенесли в переиспользуемые Composer-пакеты (Композер — менеджер зависимостей PHP). Такой подход позволил стандартизировать базовый технологический слой и сократить объём повторяющейся инфраструктурной разработки: при создании нового сервиса команда сразу переходила к реализации его предметной области и бизнес-логики.
В общий платформенный слой вошли:
- управление жизненным циклом Swoole-сервера (Соул) и HTTP-клиентами (ХТТП) с поддержкой пулов соединений;
- runtime-кэш (кэш уровня среды выполнения) и интеграции с базами данных MongoDB (МонгоДБ), Redis (Редис), PostgreSQL (ПостгреСКЬЮЭЛ), а также с системой для текстового поиска и аналитики Elasticsearch (Эластиксёрч);
- health checks (хелс-чеки — проверки состояния сервисов), структурированное логирование, трассировка и APM (Application Performance Monitoring — мониторинг производительности приложений);
- единая обработка HTTP-заголовков, идентификатора сайта, языка и контекста входящего запроса.
Бизнес-фичи и отдельные компоненты системы
API Cache (АПИ Кэш): как разгрузили публичный API
Повторяющиеся запросы к меню, справочникам, настройкам, секциям и другим данным с высокой долей операций чтения создавали дополнительную нагрузку на Laravel (Ла́равел) и PostgreSQL (ПостгреСКЬЮЭЛ). При этом большая часть таких данных изменялась существенно реже, чем запрашивалась.
На стороне сайта уже использовалось кэширование Cloudflare (Клаудфлэр — инфраструктурный сервис для доставки и кэширования контента). Однако такой подход был ограничен с точки зрения управления кэшем и сталкивался с проблемами, связанными с РКН, а высокая доля cache miss (кэш-миссов — запросов, при которых данные отсутствуют в кэше) снижала эффективность механизма.
Поэтому разработали отдельный edge-сервис (эдж-сервис — сервис на границе инфраструктуры, расположенный между клиентом и внутренними системами), который перенёс управление кэшированием на сторону инфраструктуры проекта.
Запрос обрабатывается по многоуровневой схеме:
- Сервис в начале делает проверку быстрого runtime-кэш (кэш уровня среды выполнения) внутри Swoole (Суол).
- Если данные отсутствуют, обращается к общему кэшу в MongoDB (МонгоДБ).
- Когда данные отсутствуют на обоих уровнях, запрос передаётся монолиту или целевому сервису.
- Полученный ответ сохраняется в кэш и возвращается клиенту в совместимом формате.
Какие данные сохраняются и проверяются сервисом?
Помимо тела ответа сервис кэширует его статус, заголовки, параметры сжатия и набор тегов для последующей инвалидации. Для разных маршрутов можно задавать собственные политики хранения и TTL (Time To Live — время жизни записи в кэше).
Платформенный слой также обеспечивает:
- защиту от одновременного пересчёта одного и того же ключа при резком росте количества запросов;
- проведение очистки по тегам, принудительное обновление и прогрев данных;
- очистки или обновления данных в оперативной памяти приложения (сброс runtime-кэша), которая срабатывает автоматически при возникновении определенных событий в системе;
- сбор метрик cache hit и cache miss (попаданий и промахов) на каждом уровне хранения;
- проксирование части поисковых и рекомендательных сценариев.
В результате повторяющиеся операции чтения перестали обращаться к Laravel (Ларавел) и PostgreSQL (ПостгреСКЬЮЭЛ) при каждом запросе, при этом внешний API (АПИ)-контракт витрины сохранился без изменений. Для прогретых эндпоинтов достигнута задержка 2 мс на p99-процентиле.
Индексатор каталога: витрина данных в Elasticsearch (Эластиксёрч)
Как отдельный источник нагрузки сохранили страницы категорий, фильтрацию и получение данных о товаре. Поисковые сценарии продолжила обслуживать Algolia (Алголия — поисковая платформа), а для каталожного чтения сделали самостоятельный сервис на базе Elasticsearch (Эластиксерч — поисковая и аналитическая платформа).
Catalog Indexer (Каталог Индексер — сервис индексации и чтения каталожных данных) возвращает данные в формате, который совместим с существующими контрактами монолита. Это позволяет поэтапно переводить отдельные маршруты на новый сервис без одновременной переработки потребляющего кода.
В настоящее время вынесены:
- товары, модели, категории и различные представления каталожных данных;
- фильтры, образы, комплекты и баннерные блоки;
- данные об остатках и магазинах;
- связанные и дополняющие товары.
Как система обновляется не останавливая чтение?
Индексы каталогов разделили по функционалу и сайту. На место единого крупного документа пришли обособленные read models (рид-модели — модели данных, оптимизированные для чтения), отвечающие за структуру раздела, товарную проекцию, цены, остатки и данные о магазинах.
Обновление индексов построено по двум сценариям:
- Полная индексация. Полный экспорт формирует новый вариант индекса. После завершения построения alias (алиас — логическое имя индекса) атомарно переключается на новый индекс, поэтому операции чтения не прерываются и не получают частично обновлённые данные.
- Частичная индексация. При изменении отдельных сущностей обновляются только соответствующие данные, без полного перестроения индекса.
Для обработки отказов используется circuit breaker (сёркит-брейкер — механизм автоматического ограничения обращений к недоступному сервису). Когда Elasticsearch (Эластиксерч) вне доступа или circuit breaker отмечает последовательность ошибок, для поддерживаемых маршрутов активируется fallback (фолбэк — резервный сценарий обработки) в монолит.
Такой механизм ограничивает область отказа и позволяет сохранить работоспособность отдельных сценариев. При этом fallback (фолбэк) применяется только для явно определённых маршрутов и условий, а не используется как универсальный способ скрыть ошибки нового сервиса.
Geo Service (Гео Сервис), чтобы изолировать влияние внешних провайдеров
Геопоиск применяется в сценариях выбора города, улицы и дома, определения почтового индекса и формирования адреса доставки. Время отклика в этих операциях связано с внешними API (Application Programming Interface — программный интерфейс приложения), Соответственно синхронное выполнение таких запросов в монолите напрямую влияло на ресурсы главного приложения.
Для изоляции внешних зависимостей, выделили гео контур в самостоятельный Swoole-сервис (Суол — высокопроизводительное расширение для PHP). Geo Service (Гео Сервис) определяет необходимого провайдера в зависимости от сайта и сценария, выполняет внешний запрос и преобразует полученный ответ к единообразному контракту.
В сервисе реализованы:
- адаптеры для DaData (ДаДата), Яндекс Геокодера, Post.kz (Пост кз) и сервиса определения страны по IP (АЙ ПИ);
- нормализация адресных данных с учётом различий в форматах и правилах различных государств;
- кэширование повторяющихся запросов через runtime-кэш (времени выполнения) и MongoDB (МонгоДБ);
- независимое управление очисткой геокэша;
- мониторинг и трассировка внешних вызовов для контроля задержек и ошибок провайдеров.
В результате нестабильная работа и торможение на стороне внешних геопровайдеров были изолированы от основного Larave (Ларавел)l-приложения: внешние запросы обрабатываются отдельным сервисом, а повторяющиеся обращения обслуживаются из собственного кэша.
API Gateway (АПИ-шлюз): защита критичных маршрутов
В процессе выделения отдельных сервисов потребовался единый инфраструктурный слой для маршрутизации запросов пользователей, контроля доступа и управления нагрузкой. API Gateway (АПИ-шлюз — промежуточный слой маршрутизации запросов) используется преимущественно для пользовательских сценариев и операций, чувствительных к дублям отправок.
При этом Gateway (шлюз) — это не обязательная точка входа для всего API (АПИ). Отдельные route slices (роут-слайсы — выделенные группы маршрутов) для каталога, кэшируемых данных и геосервисов могут маршрутизироваться непосредственно в сервисы.
На уровне Gateway (шлюза) реализованы:
- валидация разрешённых маршрутов и HTTP-методов (ХТТП);
- маршрутизация запросов в целевой сервис или группу экземпляров монолита;
- локальная проверка JWT (JSON Web Token — токен авторизации) по JWK (JSON Web Key — ключам проверки подписи);
- работа с подписанным device ID (айди устройства);
- rate limiting (рейт-лимитинг — ограничение частоты запросов) для высоконагруженных маршрутов корзины и заказа;
- недолговременная защита от повторной отправки запросов;
- ограничение нагрузки, когда растут задержки: retry-политики (ретраи — повторные попытки выполнения запроса) и circuit breaker (сёркит-брейкер — механизм отключения обращений к нестабильному компоненту);
- ограничение нагрузки при увеличении времени ответа downstream-сервисов;
- переключение на запасной upstream (апстрим — целевой сервис обработки запроса) для поддерживаемых сценариев;
- передача trace (трейс)-контекста для распределённой трассировки запросов.
Gateway отвечает за инфраструктурные механизмы маршрутизации, защиты и управления нагрузкой, но не переносит на себя бизнес-идемпотентность (защиту бизнес-операции от повторного выполнения). Для платёжных и заказных операций она по-прежнему реализуется внутри соответствующего бизнес-контура.
Account Facade (Аккаунт Фасад): перенос пользовательского контура
На момент интеграции с новым сервисом мастер-данных пользовательский домен находился в переходном состоянии: часть данных уже была передана Customer-сервису, а отдельные таблицы и бизнес-операции продолжали обслуживаться монолитом. Одновременный перенос авторизации, профиля, адресов, checkout (чекаут — оформление заказа) и CRM (Customer Relationship Management — управление взаимоотношениями с клиентами) создавал существенные риски для пользовательских сценариев.
Для поэтапной миграции был выделен Account Facade (Аккаунт Фасад — фасадный сервис пользовательского контура). Он агрегирует данные из нескольких источников и предоставляет витрине единый API (АПИ)-контракт. При этом он не становится новым долговременным владельцем customer (клиентского)-домена.
В контур Account Facade (Аккаунт Фасада) вошли:
- валидация JWT (JSON Web Token — токен авторизации), получение профиля и адресов из Customer (кастомер) -сервиса;
- предоставление необходимого read-only (неизменяемого)-контекста пользователя из PostgreSQL (ПостгреСКЬЮЭЛ);
- работа с избранным, сохранёнными банковскими картами и частью расчётных операций корзины;
- генерация QR (кюар)-кода пользовательского профиля;
- проксирование операций, которые на текущем этапе должны оставаться в монолите.
Как управляли миграцией?
Для контролируемого перевода пользовательского трафика предусмотрены режимы proxy (прокси — передача запроса в существующий контур), service (сервис — обработка запроса новым компонентом) и shadow (шэдоу — параллельная обработка запроса без влияния результата на основной сценарий).
Такой механизм позволяет последовательно сравнивать поведение нового и существующего контуров, затем переводить на новый сервис операции чтения или записи и если нужно оперативно возвращать трафик в монолит.
Для изоляции ресурсоёмких операций инфраструктура разделена на отдельные группы экземпляров. Профиль, избранное и генерация QR-кодов обслуживаются раздельно, поэтому пиковая нагрузка на одну группу не должна напрямую вытеснять ресурсы, необходимые для обработки остальных пользовательских запросов.
Мониторинг и наблюдаемость
В распределённой архитектуре одной итоговой метрики времени ответа недостаточно: необходимо определять конкретный участок цепочки, сформировавший задержку или ошибку — приложение, кэш, базу данных, Elasticsearch (Эластиксерч) или внешний API (АПИ). Поэтому observability (обсервэбилити — наблюдаемость системы) расширялась параллельно с выделением новых сервисов.
В инфраструктуре используются:
- структурированные логи в формате ECS (Elastic Common Schema — единая схема структурированных логов) и сквозная передача traceparent (трейс-контекста для распределённой трассировки);
- Elastic APM (Application Performance Monitoring — мониторинг производительности приложений) с отдельными spans (спанами — участками распределённого трассировочного дерева) для HTTP (ХТТП), PostgreSQL (ПостгреСКЬЮЭЛ), MongoDB (МонгоДБ) и Elasticsearch (Эластиксерч);
- Prometheus (Проме́теус)-метрики для мониторинга состояния и производительности сервисов;
- метрики cache hit/miss (попаданий и промахов кэша) для каждого уровня хранения;
- startup-, readiness- и liveness-проверки (проверки запуска, готовности и работоспособности экземпляра);
- Команды диагностики для платёжного и фискального контуров.
Такой набор инструментов обеспечивает сквозную диагностику запроса: от входящего HTTP (ХТТП)-вызова до конкретного сервиса, операции с данными или внешней зависимости, сформировавшей задержку или ошибку.
Платежи и фискализация
С весны прошлого года команда Lachestry (Лэчестри) подключилась к развитию payment-сервиса (пэймент-сервис — сервис обработки платежей) и receipt-сервиса (рисит-сервис — сервис фискализации) Lime (Лайма). Основной технический фокус — сценарии с асинхронным результатом внешней операции, когда итоговый статус не может быть получен в рамках одного синхронного запроса и требуется последующее восстановление состояния.
В рамках этих контуров реализуются:
- интеграции с Яндекс Сплит и SberPay (СберПэй), а также сценарии отмены, возврата и восстановления статусов;
- периодические проверки операций, с которыми возникли проблемы и диагностические команды;
- событийное взаимодействие через RabbitMQ (Рэббит Эм Кью) и Kafka (Кафка), операционные механизмы повторной доставки сообщений;
- обработка авансовых, полных и возвратных чеков;
- обработка callbacks (колбэков — обратных уведомлений от внешней системы) от CloudPayments (Клаудпейментс) и CloudKassir (Клаудкассир)
Архитектурно платежная операция и фискализация рассматриваются как два независимых состояния. Успешное завершение платежа не означает автоматически сформированный фискальный чек, поэтому для каждого контура предусмотрены собственные состояния, обработчики ошибок, механизмы повторной обработки и процедуры восстановления.
Платная доставка
Параллельно с развитием платформенной архитектуры, разработчики развивали бизнес-функционал онлайн-магазина. Внедрение механизма платной доставки затронуло несколько связанных контуров: расчёт корзины и заказа, применение правил бесплатной доставки, передачу информации в платёжный и фискальный контуры, расчёт НДС и интеграционные контракты.
Расширение функциональности дало бизнесу механизм управления ценой исполнения заказа.
Оценка влияния новой фичи на выкупаемость заказов, операционные расходы и другие бизнес-метрики требует подтвержденных данных со стороны Lime (Лайма), поэтому без таких данных количественные выводы не делаются.
Результат
Ключевым результатом начального этапа стала поэтапная декомпозиция самых нагруженных пользовательских контуров и уменьшение их прямой зависимости от Laravel (Ларавел) -монолита и единой операционной базы данных.
- внедрён управляемый многоуровневый API-кэш для повторяющихся операций чтения;
- часть каталожных сценариев, включая чтение и фильтрацию, вынесена на Elasticsearch (Эластиксёрч);
- внешние геопровайдеры изолированы в самостоятельном сервисе;
- для пользовательских и критичных маршрутов выделен отдельный API Gateway (АПИ-шлюз);
- запущена поэтапная миграция пользовательского контура через Account Facade (Аккаунт Фасад);
- сформирован общий платформенный слой для разработки и эксплуатации Swoole (Суол) -сервисов;
- Произошло расширение сквозной наблюдаемости параллельно с развитием монолита, платёжного и фискального контуров, а также бизнес-функциональности доставки.
Наблюдения команды проекта показывают, что последующие пиковые периоды проходили стабильнее за счёт распределения нагрузки между специализированными сервисами. Повторяющиеся операции чтения в ряде сценариев обслуживались на уровне кэша и не требовали обращения к монолиту.
При этом механизмы отказоустойчивости применяются адресно. Fallback (фолбэк — резервный сценарий обработки), резервные upstream (апстримы — целевые сервисы) и поэтапное переключение доступны только для заранее определённых маршрутов. Для каждого сценария отдельно оценивается допустимость резервного пути, особенно если операция связана с записью данных и существует риск получить неконсистентное состояние.
Что показал проект
Проект не сводился к замене технологического стека или механическому выделению сервисов из монолита. Основной архитектурной задачей стало определение устойчивых границ ответственности между компонентами системы и правил их взаимодействия.
В ходе развития архитектуры команда последовательно определяла:
- какие данные допустимо кэшировать и где должна оставаться бизнес-логика;
- какие запросы можно без риска выносить не меняя публичные контракты;
- в каких сценариях допустим ограниченный fallback (фолбэк), а где он может привести к двойной записи;
- какие операции требуют идемпотентности и механизмов восстановления;
- какие компоненты можно мигрировать поэтапно без необходимости одновременно перестраивать всю платформу.
В результате для Lime (Лайм) сформирована архитектура, которую можно развивать итеративно: новые сервисы интегрируются с существующим монолитом через контролируемые контракты и постепенно принимают на себя отдельные функциональные контуры. Lime (Лайм) и Lachestry (Лэчестри) продолжают совместную работу над интернет-магазином, совмещая развитие пользовательской и бизнес-функциональности с последовательным устранением архитектурных ограничений.
