Реанимация легаси: как мы заставили древний SEO-сервис говорить на другом языке

Страницы:  1

Ответить
 

Professor Seleznov


У каждого второго разработчика или QA есть сервис, который:
  • Написан на древней версии языка
  • Не имеет авторов
  • Тесты не работают
  • Документация — одна страница
  • Но он стабильно работает, и его все боятся трогать
А потом прилетает задача: добавить мультиязычность, или новый тип данных, или интеграцию с внешним API. И вы понимаете: либо вы его трогаете сейчас, либо он ломается сам через полгода в самый неподходящий момент.
Всем привет! На связи Даша, QA команды «Платформа Web» в Иви, и Андрей, наш разработчик. Нам достался Pyro — SEO-сервис с минимумом тестов, документации и авторов. Задача: добавить мультиязычность, ничего не сломать. Рассказываем, как мы чистили мусор, писали скрипты перевода и восстанавливали пирамиду тестов. Спойлер: у нас получилось. Поэтому если вы когда-либо сталкивались с вопросами «Как тестировать и предотвращать проблемы с SEO?» или «Как воскресить легаси сервис?», то эта статья для вас! Надеемся наш опыт вам поможет! А также будем рады, если в комментариях вы поделитесь своими мнениями и идеями. Давайте обсудим вместе!
Введение: что такое Pyro и почему мы его боялись
Pyro— это SEO-сервис, написанный на PHP 5, который хранит и отдаёт метатеги и другие SEO-данные для всего веба Иви. 
SEO в вебе — это не просто «технические настройки», а философия создания понятных, структурированных и ценных веб-ресурсов, которые: 
  • Люди могут легко читать и использовать
  • Поисковые системы могут правильно индексировать
  • Помогают владельцам получать стабильный трафик и клиентов
Наша задача: сделать сервис мультиязычным.
Проблема, с которой мы столкнулись: исторически специального параметра для языка не существовало. Все данные были на русском. Добавить поддержку языка, не сломав существующую сложную логику — вот главная задача. 
1. Знакомство с монстром: как работает Pyro
1.1. Размеры проблемы 
Pyro участвовал в формировании каждой страницы сайта. Если он падал или ошибался — мы теряли значительную часть поискового трафика на затронутых страницах. Для бизнеса это прямые потери. Для нас — жёсткий SLA.
Для понимания масштаба: одна страница → 5 запросов к Pyro. Всего роутов — 23, плюс параметризирующие GET-параметры. Ошибка в одном роуте — и трафик улетает. Вот как это выглядит на примере страницы /watch/...:
  • /group — общие метатеги для всех карточек контента (фильмы, сериалы)
  • /video — метатеги для конкретной карточки
  • /special_links, /menu — данные для шапки сайта
  • /meta — аналог robots.txt в head
Итого 5 запросов на одну страницу. А страниц — тысячи. Нагрузка на разработку и тестирование — колоссальная.
1.2. Логика работы сервиса в двух словах
Проблема:В текущей реализации нашего SEO-сервиса не была заложена поддержка нескольких языков. Данные хранятся в виде «ключ → значение», ключ строится из url а при запросе сервис собирает все ключи от общего к частному и склеивает ответы. Это порождает три классических эффекта:
  • Протекание данных — общие ключи затирают частные
  • Неявный приоритет — порядок обхода жёстко зашит в коде
  • Трудность с новыми параметрами — они ломают всю логику мержа
Логика работы Pyro:
SEO-сервис работает по одной идее — «отдать хоть что-то и как можно больше». В нем на каждый GET-запрос выполняется рекурсивный обход базы данных. Ключ в базе данных собирается из url запрашиваемого ресурса. Например, если пользователь запросил url вида/menu?host=ivi.tv с параметром определенного хоста, то ключ в БД будет выглядеть как mask/menu/ivitv.

Пример ответа БД по ключу

Пример ответа БД по ключу - mask/menu:
{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "111_linktitle": "ONLY IVI RU TEXT",
    "162_text": "DEFAULT TEXT",
    ...
}
Пример ответа БД по ключу - mask/menu/ivitv:
{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "162_text": "ONLY IVI TV TEXT",
    ...
}
Ключ делится по слешам и ищется слева-направо. Например, в HTTP запросе к /menu/?host=ivi.tv, Pyro запросит ключ — mask/menu/ivitv, сделает два запроса в БД — по ключам mask/menu и mask/menu/ivitv, смержит ответы от БД и отдаст это в ответе.
{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "111_linktitle": "ONLY IVI RU TEXT",
    "162_text": "ONLY IVI TV TEXT",
    ...
}
1.3. Как редакторские изменения SEO-данных попадают на прод
При необходимости внести какие-либо изменения в разметку страницы на сайте, редактор будет вносить ее непосредственно в файлы Pyro-updater.
Pyro-updater — это «админка» для работы с данными Pyro. Представляет из себя репозиторий в гите со специальным CI. Реплицирует структуру базы данных в файловом виде. Обновляет данные внутри Pyro («прожигает») посредством PUT-запросов из CI гита. Из yaml файла берется key — это url, по которому выполнится запрос и будет собираться ключ в БД, и value — это SEO-данные для конкретной сущности. Ниже представлен пример такого файла для главной страницы Иви:
pic
Рис.1. Файл с SEO данными
Когда пользователь попадет на страницу, где менялась СЕО-разметка, сайт отправит GET-запрос на url указанный как ключ в файле Pyro-updater'а, и отдана информация обновленная из ранее описанного PUT-запроса. В целом, диаграмма работы Иви с сервисом выглядит следующим образом:
pic
Рис.2. Silex-диаграмма работы сервиса со стороны редактора SEO-данных.
pic
Рис.3. Silex-диаграмма работы сервиса со стороны пользователя Иви.
2. План спасения
Вместо одной большой задачи «Добавить мультиязычность в Pyro» мы создали эпик с чёткими шагами:
1. Исследование 
   - Разработчик: разобраться в логике работы, поднять локально
   - QA: провести аудит тестового покрытия, изучить баги за год, собрать список роутов Pyro по приоритету
2. Чистка данных 
   - Проверить на наличие неиспользуемых файлов
3. Автоматизация процессов
   - Подготовить скрипты для машинного перевода, поиска в базе файлов дубликатов и поиска отличий внутри языковых директорий
4. Добавление url-параметра языка в запросы сервиса — lang
   - Изменить логику формирования ключей
3. Разработка
3.1. Чистка данных: удаляем мусор
Репозиторий Pyro-updater (админка для SEO-данных) представлял из себя ~550 МБ чистого текста. Многие данные дублировались или не использовались. Существовали специальные параметры, дробящие пользователей на группы, например,authorized— деление на авторизованных и не авторизованных. 
Что вырезали:
- Файлы, содержащие в url устаревшие GET-параметры для авторизованных подписчиков и не подписчиков, что сильно увеличивало объем. 
- Устаревшие маски-плейсхолдеры для удалённых разделов сайта
- Дублирующиеся JSON-LD разметки — для этого был написан анализирующий скрипт, выдающий список файлов, где и что дублируется
Итог: минус ~150 МБ «мусора», упрощение структуры.
3.2. Добавление параметра языка lang: осторожно рекурсия
Главная особенность Pyro — рекурсивный обход + мерж данных. Если не осторожничать, русские данные «протекают» в другие языки через общие маски.
Наше ключевое решение: параметр lang не участвует в рекурсии.
Почему это было больно? Покажу на примере карточки контента.
Как работало раньше (без языков):
  • PUT в /group/{content}/ → {"title": "Общий заголовок"}
  • GET на /video/{id}/ → получает тот же заголовок (через мерж)
Как стало (с ?lang=uz):
  • PUT в /group/{content}/?lang=uz → ок
  • GET на /video/{id}/?lang=uz → пусто. Мержа для разных языков нет.
Последствия: 4 самых приоритетных роута Pyro начали отдавать заглушки вместо реальных данных. Редактор SEO был не в восторге — пришлось бы вручную заполнять теги для каждого контента.
Что дальше? Мы думали вернуть мерж обратно. Но поняли: переписывать логику мержа — значит рисковать всеми роутами. Этого мы себе позволить не могли, поэтому выбрали другой путь — разработали скрипты для массового перевода и контроля качества. 
3.3. Инструменты: скрипты для массового перевода и финального контроля качества
Для машинного перевода был использован уже знакомый команде сервис SmartCat. Мы написали скрипты для выполнения следующих задач:
  • Подготовка данных для загрузки в SmartCat. Скрипт рекурсивно собирает большой объём глубоко вложенных файлов в единую структуру, делит их на мелкие чанки для загрузки в SmartCat (мы не можем загнать огромный объем данных за раз)
  • Подготовка полученных от SmartCat переводов. Скрипт соединяет разделенные ранее блоки в единый файл и добавляет параметр языка переведенным данным
  • Скрипт, сравниващий структуры данных в разных языковых директориях и копирующий недостающие файлы. Так мы решили проблему ручного ввода SEO-данных админом
  • Проверка отсутствия кириллицы в финальных переводах для анализа корректности проделанной работы
4. Тестирование: восстанавливаем пирамиду для SEO-сервиса
Для начала поговорим о проверках, которые могут быть не очевидны для тех, кто раньше не работал с SEO.
4.1. Базовые проверки SEO для чайников
Кейс 1. Глазами бота
Зачем: если бот получит 500 или 404 — страница выпадет из индекса. Теряем трафик.
Как проверить:
  • Chrome DevTools → Network conditions → User agent → выбрать Googlebot Smartphone (или YandexBot)
  • Обновить страницу
На что смотреть:
  • Страница открылась
  • Метатеги на месте
  • Нет 500, нет 404
  • Нет редиректа (если хотите скрыть раздел — лучше 404)
Кейс 2. Сервис упал — что видит пользователь
Зачем: убедиться, что при падении Pyro страница не рассыпается.
Как проверить: заблокировать запрос к Pyro (сниффер), обновить страницу.
На что смотреть: страница открыта, метатеги заменены на статику (не пустые).
Кейс 3. Сервис упал — что видит бот
Зачем: бот должен получить 503 (временная проблема), а не 500. Это сохранит позиции в индексе.
Как проверить:
  • Заблокировать запрос к Pyro
  • Подставить User-Agent бота (Googlebot / YandexBot)
  • Обновить страницу
На что смотреть: страница отдаёт 503, а не 500
Кейс 4. Данные из Pyro дошли без искажений
Зачем: E2E-проверка, что фронтенд не перебил ответ сервиса.
Как проверить: для ускорения тестирования мы используем расширение SEO META in 1 CLICK, которое позволяет увидеть заполненные теги на странице. Также для проверки на мобильных устройствах мы используем собственное расширение, так как SEO META in 1 CLICK не позволяет проверить данные на телефоне.
На что смотреть: метатеги на странице = ответу Pyro.
Кейс 5. Проверка данных в JSON-LD 
Зачем: поисковые системы используют JSON-LD для понимания структуры страницы (фильм, сериал, персона, отзыв). Ошибки в разметке → неправильный сниппет в выдаче → падение кликов.
Как проверить:
  • Chrome DevTools → Elements → найти 
  • Или расширение SEO META in 1 CLICK (вкладка Structured Data)
  • Скопировать JSON и проверить валидатором (например, validator.schema.org)
На что смотреть:
  • Тип разметки соответствует содержимому страницы (MovieTVSeriesPerson и т.д.)
  • Обязательные поля (nameurlimage) не пустые
  • Нет синтаксических ошибок в JSON
Кейс 6. Наличие атрибута lang в HTML в соответствии с языком страницы
Зачем: поисковики учитывают lang при ранжировании для конкретного языка. Скринридеры используют его для выбора правильного произношения.
Как проверить:
  • Chrome DevTools → Elements → найти  или 
  • Либо вручную посмотреть исходный код страницы
На что смотреть:
  • Атрибут lang присутствует и соответствует языку страницы (ru, uz, en и т.д.)
  • Если страница мультиязычная — lang меняется при переключении языка
  • og:locale синхронизирован с lang
4.2. Подготовка к тестированию
В Иви подготовка к тестированию включает в себя множество этапов: аудит покрытия, изучение багов, изучение пользовательких сценариев, построение графиков по посещениям на страницах с сервисом, архитектура покрытия на основе полученных данных и т.д. Предлагаю бегло пройтись по этапам.
Аудит покрытия (шокирующие цифры) 
После небольшого экскурса в проверки SEO хотелось бы обсудить само тестовое покрытие, варианты его оптимизации. Понимаю, что перевернутой пирамидой тестирования никого не удивить, но хотелось бы этот пункт посвятить именно ей. У нас были написаны тест-кейсы на проверки для SEO, о которых я рассказала выше в пункте 4.1. Часть кейсов была покрыта автотестами, но достаточно ли нам этого? Как оказалось, нет, и вот почему:
  • Масштаб сервиса и важность передаваемой им информации (см.пункт 1.1) требуют покрыть все роуты хотя бы базовыми проверками. Например, проверка GET и PUT запросов, проверка мержа данных и т.д.
  • Как выяснилось долгое время в CI не запускались написанные ранее API и Unit-тесты, и это лишь часть проблемы
  • Уровень найденных авто, API и Unit тестов был совсем «базовый минимум». «Роскошного максимума» никто и не хотел, но важно было сократить количество ручных проверок:
    • Многие GET-параметры не были учтены, что явно добавляло ручных проверок с учетом параметризации
    • Мерж данных между разными роутами вообще не проверялся, хотя является одной из важнейших фич этого сервиса
    • Проверки рекурсивного обхода также не было
  • UI-тесты редко содержали в себе проверки полученных данных из Pyro. Часто падали, требуя ручной перепроверки
  • В ручных тест-кейсах проверялись лишь сценарии из пункта 4.1. То есть количество кейсов здесь не равно качественной проверке, т.к. напрямую сам сервис не проверялся
Состояние «до»:
  • Кейсы проходимые вручную: 57
  • UI-тесты: 93  
  • API-тесты: 70 
  • Unit-тесты: 131  
Главная проблема: Не соблюдается принцип пирамиды тестирования. Скорее у нас были хрень какая-то песочные часы. Много хрупких UI-тестов, мало стабильных API и Unit-тестов.
Построение графиков по посещениям страниц с сервисом
Для построения графиков использовали инструмент Grafana. Нашей целью было узнать:
  • На каких страницах больше всего пользователей
  • На какой платформе больше всего нагрузки
  • Как часто и какие боты к нам приходят 
После получения данных мы можем точно понять, какие роуты нуждаются в большем внимании при написании кейсов и каковы будут потери при пропуске каких-либо сценариев. В нашем случае самой важной оказалась карточка контента, а именно сериалы, на desktop версии сайта.
Архитектура тестового покрытия
Изучили баги, собрали сценарии от бизнеса. Тест-кейсы разделили на API (unit-тесты) и e2e (UI-автотесты). Примерная иерархия покрытия тест-кейсами:
  • Pyro
    • Общие проверки — проверки, подходящие любому роуту
      • GET-параметры — проверки не привязанные к роуту. Проверяются на списке роутов
      • Негативные проверки — общие негативные проверки, подходящие любому роуту
    • Роуты — кейсы учитывающие особенности одного отдельно взятого роута или его слияние данных с другим роутом
      • /route_1 
      • /route_2
Такая иерархия решает сразу несколько проблем в покрытии:
  • Удобство прохождения ручных проверок и понятная параметризация в каждом сценарии. Теперь все кейсы, где неважен роут, а важен GET-параметр, вынесены в один блок.
  • Только важные пользовательские сценарии внутри директорий по роутам. Что сокращает количество сценариев, а значит ускоряет регресс.
  • Понятное распределение кейсов по уровням пирамиды и полноценное понимание уровня покрытия сервиса.
Такой комплексный подход в тестировании помогает опираться не только на знания QA, но и на реальные данные, а соответственно строить более устойчивое тестовое покрытие.
Автоматизация рутинных проверок
Автоматизация — одно из прекраснейших созданий разработки. Поэтому не пренебрегайте ею. Вместо ручной проверки метатегов расширением, мы написали скрипт, который:
  • Ходит по списку важных страниц
  • Запрашивает данные из Pyro для разных языков
  • Сравнивает с эталонными шаблонами
  • Формирует отчёт в CI
Итог всей проделанной работы:
  • Кейсы проходимые вручную: 37
  • UI‑тесты: 93 → 95 (удалили дублирующиеся, добавили важное)
  • API‑тесты: 70 → 164
  • Unit‑тесты: 131 → 315
Вывод: сместили фокус на низкие уровни, регресс ускорили в 3 раза. Также мы получили выхлоп в виде сокращения ЧЧ QA в 10 раз. Т.к. инженерам по обеспечению качества оставалось только проверить приоритетные роуты в Pyro.
5. Интересные нюансы разработки и тестирования для SEO
Особенность нашего приложения — BFF 
Фронтенд Иви имеет полностью самописный SSR с клиентскими переходами. Переходы на клиенте сделаны при помощи BFF, отдающим данные для запрашиваемой страницы. BFF запрос может не получить информацию о языке и отдать данные на русском. А это может подпортить пользовательский опыт из-за частичного перевода некоторых блоков на страницах.
Решение: Добавили язык в роут для всех внутренних запросов к BFF.
Проблемы с машинным переводом
Переводя большие объемы данных на новый язык мы столкнулись с проблемой, что SmartCat, умеющий работать с латиницей в обычных названиях и ссылках, не корректно считывал поле, содержащее список ссылок подряд. Он переводил его вместе с основным текстом, что приводило к ошибке 404 при переходах на клиенте. 
Решение: Проконсультировавшись с руководителем SEO было принято решение вырезать это поле во всех переводах. Дополнительно нам помог скрипт анализа кириллицы в финальных переводах из пункта 3.3. 
Пример покрытия для тестирование рекурсивного обхода и слияния данных
Рассмотрим на примере одного запроса: /group/category/{category}. Для начала пишем кейсы на основе данных, багов и пользовательских сценариев. Главные правила:Сначала постарайтесь ответить на вопрос "на какой уровень будет тест, если я его напишу?" и выбирайте уровень как можно ниже.
pic
Рис.4. Кейс для итеграционного теста выше
После всех мучений с кейсами создаётся задача на покрытие в коде, и если вы готовы писать тесты и умеете это делать, то не стесняясь приступайте! В нашем случае тесты написаны с использованием фреймворка phpunit.
pic
Рис.5. Пример интеграционного теста
5. Заключение
Количественные изменения:
  • Тестовое покрытие — восстановили пирамиду тестирования (см. таблицу ниже)
  • Мусор в Pyro-updater — больше нет дублирующих и неиспользуемых файлов. Вес снизился с ~550 до ~400 МБ
Качественные изменения:
  • Pyro теперь мультиязычный — поддерживает ru, uz и готов к быстрому добавлению новых языков
  • Понимание сервиса — у команды появилась документация и тестовое покрытие
  • Стабильность — добавили в CI тесты, релизы перестали быть русской рулеткой благодаря тестовому покрытию
pic
Наш главный неочевидный совет, который мы поняли к концу проекта:
Не пытайтесь понять весь легаси-сервис целиком. Вместо этого:
  • Найдите одну самую важную страницу/ручку, которую сервис обслуживает (у нас — карточка контента)
  • Напишите один сквозной тест на неё (запрос → ответ → отображение)
  • Зафиксируйте результат как эталон
  • Только потом расширяйтесь на другие роуты
Мы потратили 3 недели на полный разбор всех роутов и поняли, что 70% времени ушло на то, что почти не используется. Если бы начали с карточки контента, то сократили бы исследование в 2 раза.
Как итог: архив знаний по Pyro был воскрешён. Сервис заговорил на узбекском, а в перспективе — и на других языках. Теперь команда не боится его, готова к активному развитию и обновлению сервиса. Теперь у нас есть тесты, документация и CI. Сервис больше не чёрный ящик. Это история о том, что даже с самым забытым legacy-сервисом можно подружиться. Нужно только пройти через боль, гнев и тонны тестов. И помните: если у вас есть сервис, который работает, но его все боятся трогать, то велика вероятность, что однажды это придется сделать именно вам.
А теперь вопрос к вам: какой самый древний язык/фреймворк вы оживляли? Сколько лет было сервису? И главное — вы всё переписали или оставили как есть? Жду в комментариях ваши истории и стадии принятия. В общем, попрощаемся, как обычно: не стойте на месте, с удовольствием изучайте новое и улучшайте себя! До новых встреч на Хабре!-Источник
 
Loading...
Error