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. Маппер Битрикс-ответ → нормализованный контракт. Поля: id, name, slug, price, in_stock, images[], 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. Компоненты: CategoryView, ProductCard, FiltersSidebar, SortDropdown. 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_id, endpoint, status, bytes, latency_ms, data_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_view, add_to_cart, filter_click, sort_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:
- Какая страница даст наибольший выигрыш по конверсии/выручке? (Обычно категорийка.)
- Есть ли у вас API-слой или его надо ставить с нуля?
- Готова ли инфраструктура к Canary (reverse proxy, cookie-rollout, алерты на метрики)?
- Кто в команде будет owner этапа?
48-часовой аудит отвечает на эти 4 вопроса + возвращает конкретный план этапа 1 для вашего стека.