Strangler Fig на практике: разбор миграции одной категорийки по коммитам

Автор: WebGoodPeople

В прошлой статье (9 июня) разбирали strangler fig как стратегию: почему big-bang проваливается и как выглядит 6-этапный план в календаре. Эта статья — зум внутрь первого этапа. Что конкретно коммитится, какие решения принимаются по пути, где критичные точки для rollback.

Для конкретики возьмём один клиентский кейс: e-commerce на Битрикс, каталог 45k SKU, TTFB категорийки 1100 мс. Задача — мигрировать только /catalog/* на Next.js за 6 недель, без простоя.

Детали проекта обобщены, компания не называется.

Неделя 0: что есть до старта


  • Битрикс обслуживает весь сайт включая /catalog/*
  • Пиковый трафик — 800 RPS
  • TTFB категорийки: p50=700мс, p95=1100мс, p99=2400мс
  • Поиск: встроенный Битрикс (FULLTEXT)
  • Команда: 2 фронтендера (React опыт), 1 бэк (Битрикс/PHP), 1 DevOps

Неделя 1: API-слой + reverse proxy


Commit 1. Создан Node.js сервис catalog-api (Fastify + TypeScript). Один endpoint: GET /api/v1/catalog/:slug. Проксирует в Битрикс REST, кэширует ответ в Redis на 60 секунд, возвращает нормализованный JSON.

Commit 2. Маппер Битрикс-ответ → нормализованный контракт. Поля: idnameslugpricein_stockimages[]attributes{}variants[]. Нормализация важна: старый фронт будет читать через этот же контракт постепенно.

Commit 3. Nginx-конфиг с reverse proxy правилом:

location /catalog/ {
    # По умолчанию — Битрикс
    proxy_pass http://bitrix-upstream;
    
    # Но: если есть header X-Rollout-Group: new-frontend → Next.js
    if ($http_x_rollout_group = "new-frontend") {
        proxy_pass http://nextjs-upstream;
    }
}


Commit 4. Cookie-based rollout assignment. Новый .js в head-секции шапки сайта: если у юзера нет rollout_group cookie — дать его рандомно (95% legacy, 5% new-frontend). Cookie живёт 30 дней (иначе один юзер прыгает между фронтами и теряет стейт).

Выход недели 1: новый API-слой работает на staging. Nginx готов роутить по cookie. 0% трафика ещё не на новом.

Неделя 2: Next.js страница категории на staging


Commit 5. Создан Next.js проект (App Router). Одна страница: app/catalog/[slug]/page.tsx. Server Component, fetch из catalog-api, ISR с revalidate 60s.

export default async function CategoryPage({ params }: { params: { slug: string } }) {
  const res = await fetch(
    `${API_URL}/catalog/${params.slug}`,
    { next: { revalidate: 60 } }
  );
  const category = await res.json();
  return <CategoryView data={category} />;
}


Commit 6. Компоненты: CategoryViewProductCardFiltersSidebarSortDropdown. Tailwind для стилизации. Дизайн — 1:1 с Битрикс-версией (клиент не хочет редизайн на этом этапе).

Commit 7. generateMetadata для SEO: title, description, canonical, OG-теги. Canonical — на основной домен, идентичный Битрикс-версии. Важно: оба фронта должны отдавать одинаковый canonical пока идёт rollout.

Commit 8. Unit-тесты рендера страницы с mock данными от catalog-api. Быстрый smoke-тест в CI.

Выход недели 2: страница работает на staging.example.com/catalog/{slug}. Визуально неотличима от продакшена.

Неделя 3: параллельная аналитика + observability


Эта неделя — самая важная. До того как любой процент реального трафика пойдёт на новый фронт, нам нужно уметь сравнить «старое» и «новое» по метрикам.

Commit 9. API-лог по схеме из предыдущих статей: req_idendpointstatusbyteslatency_msdata_version, плюс rollout_group (значение legacy или new-frontend). Логи идут в Loki.

Commit 10. Grafana-дашборд с двумя рядами графиков, один для rollout_group=legacy, второй для new-frontend. Метрики: p50/p95/p99 latency, error rate, средний bytes, throughput. Визуально видно разницу, когда она появится.

Commit 11. Event tracking на фронте (оба фронта): page_viewadd_to_cartfilter_clicksort_change. Yandex.Metrika + собственный ClickHouse-sink с полем rollout_group.

Commit 12. Алерты:

- alert: NewFrontendErrorSurge
  expr: |
    rate({rollout_group="new-frontend", status=~"5.."} [5m])
    > 3 * rate({rollout_group="legacy", status=~"5.."} [5m])
  for: 2m
  labels: { severity: page }
  annotations:
    summary: "New frontend error rate 3x higher than legacy — auto-rollback"


Плюс аналогичные алерты на latency p95 (>1.5× baseline) и conversion drop (>10%).

Commit 13. Auto-rollback скрипт: при срабатывании алерта — webhook на GitOps-репозиторий, который меняет cookie-rollout процент обратно на 0%. Время от алерта до rollback — 30–60 секунд.

Выход недели 3: observability готова. Можем точно сказать, как ведёт себя новый фронт в сравнении со старым.

Неделя 4: Canary 5%


Commit 14. В cookie-rollout-assignment скрипте меняем процент с 0% на 5%. Деплой.

С этого момента примерно 5% пользователей (stable по cookie) попадают на новый фронт.

Первые 4 часа. Смотрим на дашборд каждые 15 минут.

  • p95 latency новый: 340ms
  • p95 latency старый: 1080ms
  • Error rate оба: < 0.05%
  • Conversion add-to-cart: новый 6.8%, старый 5.2%

Первые 24 часа. Смотрим поведение ночью (отдельный трафик-профиль от пиковых часов).

  • Всё стабильно. ISR cache работает.

Первый инцидент. На вторые сутки получаем алерт: bytes < 200 на эндпоинте /api/v1/catalog/bytovaya-tehnika. Оказалось: у категории Бытовая техника в Битриксе есть вложенные подкатегории с особой структурой, которую наш mapper не обработал. Возвращает пустой массив товаров.

Фикс: commit 15. Добавили обработку вложенных категорий. Реиндекс кэша. Проверяем — ок.

Время от алерта до фикса: 40 минут. Без этого алерта мы бы не знали о проблеме сутки.

Продолжаем на 5% ещё 3 дня. Выходные включительно.

Неделя 5: Ramp-up 5% → 20% → 50%


День 1. 5% → 20%. Смотрим дашборд 6 часов подряд. Всё чисто. Выходим до конца дня.

День 3. 20% → 50%. Здесь проявляется новая проблема: на 50% трафика ISR-кэш Next.js не успевает прогреться — первые минуты после деплоя latency скачет. Commit 16: warm-up скрипт, который при старте деплоя делает fetch всех топ-100 категорий, чтобы они легли в кэш.

День 5. Всё стабильно. Готовим переход на 100%.

День 7. 50% → 100%.

Выход недели 5: 100% трафика категорийки на новом фронте.

Неделя 6: стабилизация + cleanup


Commit 17. Старый фронт /catalog/* пока не удаляем. Остаётся в standby как rollback-путь. Cookie-rollout остаётся в конфиге, выставлен в 100% new-frontend.

Commit 18. Документация: как работает новый catalog-api, как устроен маппер, как добавить новую категорию, как запустить rollback (одна команда в конфиге).

Commit 19. Метрики за первые 2 недели 100%-ного трафика:

  • TTFB p95 категорийки: 340мс (было 1100мс)
  • Conversion через категорийку: +22% (было 5.2%, стало 6.35%)
  • Shadow revenue unlock: ≈800к ₽/мес
  • Никаких инцидентов с потерей заказов

Commit 20. Через 4 недели стабильной работы — удаление старого фронта /catalog/* из Битрикс-шаблонов. Код остаётся в git и в rollback-контейнере ещё 6 месяцев.

Итоги этапа 1


  • Календарь: 6 недель
  • Инженерная работа: ~4 недели чистого времени (2 фронтендера × 2 недели + 1 бэк × 1 неделя + 1 DevOps × 1 неделя)
  • Бюджет: ≈1.2 млн ₽ (проект, не люди-на-полную-ставку)
  • Результат: +22% конверсии на категорийке = +800к ₽/мес выручки
  • ROI: 1.5 месяца окупаемости

Что важно — на этом этап 1 останавливается. Этап 2 (карточка) — отдельное решение, отдельный пилот, отдельное согласование. CTO может сказать «нам достаточно» и не делать этап 2. У бизнеса всё ещё на +800к ₽/мес.

Что НЕ делали


  • Не мигрировали корзину. Она осталась на Битриксе, работает через iframe на страницах нового фронта.
  • Не редизайнили страницу. 1:1 с Битрикс-версией. Редизайн — отдельный проект после миграции.
  • Не меняли SEO-структуру. URL-ы, meta, canonical, sitemap — всё идентично Битриксу.
  • Не трогали админку. Менеджеры работают в привычной Битрикс-админке.

Каждое «не делали» — это scope, который мог бы утопить проект. Strangler fig как pattern требует дисциплины в удержании фокуса.

Как применить к вашему стеку


Ключевые вопросы для старта этапа 1:

  1. Какая страница даст наибольший выигрыш по конверсии/выручке? (Обычно категорийка.)
  2. Есть ли у вас API-слой или его надо ставить с нуля?
  3. Готова ли инфраструктура к Canary (reverse proxy, cookie-rollout, алерты на метрики)?
  4. Кто в команде будет owner этапа?

48-часовой аудит отвечает на эти 4 вопроса + возвращает конкретный план этапа 1 для вашего стека.


webgoodpeople.com/ru/products/headless-next-elasticsearch

Strangler Fig на практике: разбор миграции одной категорийки по коммитам — WebGoodPeople