top of page
Фото автораВладислав Поздняков

Історія одного сансету та п’яти вигорань або як мігрувати Legacy



Що робити з Legacy-системою, яка вже багато років не підтримується, а в команді майже не залишилося людей, які б памʼятали нюанси та edge-кейси розробки 15-річної давнини? 

Владислав Поздняков, Software Engineer в MacPaw, який має понад 10 років досвіду в Back-end розробці, поділився кейсом міграції такої системи. Команда працювала над нею близько року, обробляючи величезні обʼєми даних: створили понад 12 млн нових активаційних ключів, перенесли дані понад 4,5 млн користувачів і згенерували понад 7 000 ордерів для реселерів з 7,5 тисячами рідім-кодів. 


Під час конференції PHP fwdays'24 він розповів, з якими викликами та проблемами зіткнулася команда, а також як підготуватися та мінімізувати ризики. Переказуємо найцікавіше з виступу Владислава.



Legacy, про яку всі забули


Близько 15 років тому в MacPaw була написана система ліцензування та активації застосунків. Вона містила інформацію про продукти та користувачів, певну логіку платежів, обробку підписок, валідацію ліцензій, активації та навіть окремий функціонал для реселерів. Вона була написана на PHP 5 з базою даних MySQL. Крім того, у ній знайшовся такий екзотичний на сьогодні інструмент як Gearman, про який раніше я тільки читав і ніколи не зустрічав на практиці. 


Команда, що розробляла цю систему, давно була перекинута на інші проєкти. Протягом багатьох років цей продукт майже не підтримувався, що призвело до накопичення великого технічного боргу. Крім того, за цей час у нас зʼявився дещо подібний сервіс зі схожим функціоналом, а підтримувати дві системи було неефективно. Отже, ми вирішили припинити підтримку Legacy та перенести всі активації та ліцензії в новий сервіс.


Існує два основних підходи до міграції Legacy-систем. Перший — це поступова міграція, де ми відокремлюємо окремі функціональні частини старої системи та переносимо їх у нові сервіси. Так можна одночасно продовжувати бізнес-розробку, але така міграція може тривати дуже довго. Другий підхід, — його обрала наша команда, — це створення нової системи, яка б повністю замінила Legacy. Переваги очевидні: можна швидше рухатися, врахувати помилки, впроваджувати нові технології. Проте такий підхід потребує більше уваги інженерів, тому поєднувати міграцію з бізнес-розробкою може бути складно. 


Нова система була побудована на більш актуальному стеку: PHP 8, Symfony, Postgres, Redis, Docker, RabbitMQ. Ми також впровадили патерн CQRS (Command Query Responsibility Segregation) та архітектуру Onion, адаптовану під наші потреби. І що найважливіше — навколо цієї системи сформувалася сильна команда розробників, готова до викликів.

Спершу ми створили план добровільної міграції користувачів. Ідея була така: коли вони активуються в системі своїм старим ключем, ми пропонуємо їм перейти у нову систему та заохочуємо додатковими бенефітами. Проте отримали умову від менеджменту — користувачі взагалі не мають помітити змін. 


Намалювавши поверхневий пайплайн, ми знайшли рішення, як перенести всі ліцензії, активації та продукти в автоматичному режимі. Щоби оцінити естімейти, ми почали розбиратися, як налаштовані процеси у старій системі та структура даних. Для цього навіть досліджували версії застосунків, що не оновлювалися 5-6 років на старих ноутбуках, дивилися, які протоколи спілкування та версії API там використовуються. Також писали стос скриптів, щоби перевірити, які взагалі ліцензії та продукти ще актуальні, а у яких термін дії вже закінчився. 


У результаті ми зробили поверхневу оцінку кожного етапу та сформували оптимістичний та песимістичний прогнози за естімейтами. Результат буде десь посередині, думали ми. Проте менеджмент затвердив оптимістичний варіант, і ми почали міграцію.





Міграція. Початок


Першим завданням було підняти новий сервіс та API-активації, які працювали б з мігрованими ліцензіями. Проблема полягала в тому, що принципи активації продуктів у старій та новій системі відрізнялися. В одній з них користувач отримував ключ після покупки, в іншій – просто логінився через акаунт. 


Частина застосунків мала 15-річну історію. Проте якщо користувач придбав пожиттєву ліцензію, ми не можемо припинити підтримку цього продукту, яким би старим та незручним він не був. Фактично цими продуктами могло користуватися дуже обмежене коло людей, але щоби уникнути юридичних ризиків, ми мали створити новий сервіс для збереження ключів і API для активації, що підтримувало б старі продукти. 


Крім того, що Legacy-система була написана старими підходами, часом з ігноруванням важливих принципів, там використовувався один endpoint. Він всередині визначав, яка це дія — активація, деактивація чи перевірка на актуальність, а також який протокол спілкування використовується в різних версіях застосунків (їх було близько пʼяти). Ми мали розібратися в цьому, розділити на окремі логічні частини й зберегти повну функціональність. Під час розбору цього Legacy у нас сталося перше вигорання розробника, але ми продовжили. 



Поєднуємо дані між двома системами


Наступний етап полягав у тому, щоби зіставити дані між двома системами. Це виглядало досить непросто, оскільки між старою та новою системою була низка структурних відмінностей.


У Legacy була така сутність, як ключ. Ордер міг бути частиною реселерського пакету і містити декілька продуктів (бандл) або один, а також був прив’язаний до користувача. Під цей ордер створювався ключ, в якому були дані активації та все інше. У новій системі ми працювали з окремим продуктом. Кожен з них мав свій план, SKU, і був прив’язаний до інформації про оплату та ліцензії.




Отже, нам потрібно було вирішити дві відмінності: 

  • привʼязка ліцензії до користувача, а не ключа;

  • відсутність поняття «бандл» продуктів у новій схемі.


Для цього ми створили схему, яка дозволила нам зіставити старі продукти з новими ліцензіями відповідно до типу та прив’язати до користувачів. Якщо користувач не існував у новій системі, ми його створювали, якщо ж був — прив’язували до наявного облікового запису. Також ми зберегли можливість посилатися на старі ключі для активації, пов'язуючи їх із новими ліцензіями.





Будуємо пайплайн перенесення даних 


Наступним кроком стало створення пайплайну перенесення даних. Він складався з окремих етапів, кожен з яких відповідав за перенесення певної сутності. Пайплайн було загорнуто в транзакції, щоб у разі помилки можна було відкотити зміни та відновити роботу. У міру запуску ми стикнулися з edge-кейсами, які не врахували від початку. Нам доводилося працювати з уривками знань про те, що відбувалося у цій системі раніше. 


Міграцію ми запускали через Symfony Messenger, піднявши окремий пул воркерів, який можна масштабувати незалежно від основної системи. Як це працює: ми створюємо команду, яка ініціалізує перший меседж у Command Bus (кількість таких меседжів відповідає кількості воркерів). Потім передаємо, який саме бандл хочемо мігрувати. Він звертається до бази Legacy, резервує ордер для подальшої обробки й переносу. Далі запускається пайплайн і проходить всі необхідні етапи. Після відпрацювання меседж надсилається повторно, щоби воркер міг перевірити, чи є новий ордер для резервування. Таким чином, процес повторюється автоматично. Якщо нового ордеру немає, воркер виходить з циклу і надсилає повідомлення в Slack про завершення роботи.


Такий напівручний режим потрібен був нам, щоби контролювати, що переносити та в якій кількості. Наприклад, щоби не виникало ситуації, коли в пʼятницю ввечері ми запускаємо міграцію важливого складного бандлу і розгрібаємо проблеми на вихідних.



Перенос трафіку


Ми не могли вносити зміни у Legacy-продукт, а також не могли бути впевненими, що всі користувачі працюють з останньою версією продукту чи SDK. Тому ми вирішили фактично підмінити домен, який раніше дивився на Legacy, і перенаправити його на новий сервіс. Ендпоінти та респонси збігалися, а самим процесом можна керувати через Ops-команду. Такий перехід можна було здійснити на нашій інфраструктурі з мінімальним часовим лагом. 

Проте ми не могли перемкнути весь трафік в один момент, оскільки під час міграції частина даних залишалася в старій системі, а інша вже була перенесена в нову. Для цього ми створили проксі, який перевіряє кожен ключ: якщо він вже в новій базі — дані обробляються новою системою, якщо ні — проксі передає запит до Legacy-сервісу.


Також ми додали Full Proxy Mode, який обробляв весь трафік, який приходив на ендпоінт. Це допомогло мінімізувати навантаження на нову базу даних на ранніх етапах міграції та дозволяло швидко перемкнутися у разі виникнення критичних проблем. Налаштування проксі зробили максимально простим: через змінні середовища у конфігах, з можливістю швидкого перезапуску сервісу.





Тестування: нові пригоди


Нові фічі та API для активації продуктів тестувалися відносно просто, хоча були певні труднощі зі старими програмними ліцензіями. Найскладнішим виявилося тестування самої міграції даних через відсутність чітких вимог та велику кількість накопичених edge-кейсів. Щойно ми зʼясовували, що базовий флоу працює, виявлялося, що в якоїсь ліцензії все налаштовано по-іншому. Ми намагалися протестувати якомога більше різних бандлів, планів, комбінацій та виправити більшість мінорних кейсів. Але попри всі зусилля, ми не могли бути впевненими на 100%, що покрили всі можливі варіанти. Водночас змігрувати базу в 12 мільйонів ордерів заради тесту — сумнівна задача. Тому ми просто дійшли до етапу, коли стартувати міграцію було вже не страшно. Щоби впевнитися, що система здатна впоратися з очікуваним трафіком, ми провели тестування навантаження.


Отже, ми були готові до Дня Х. 



Запуск міграції та перше падіння 


Ми обрали поетапний підхід, починаючи з міграції найстаріших і найменш актуальних продуктів. Так можна було відстежувати проблеми, перш ніж переходити до більш критичних і популярних продуктів. Загалом процес йшов добре. Ми фіксили мінорні кейси, іноді доводилося зупиняти міграцію, щоби виправити баги, оптимізувати запити чи прискорити систему. Через це процес трошки затягувався, але система працювала стабільно, і ми поступово розганялися. 


Закон Мерфі для девелоперів виглядає так: якщо щось має масштабно «впасти», то це обовʼязково трапиться в суботу ввечері. На той момент ми мігрували один з найбільш актуальних продуктів з новими ліцензіями та високим навантаженням. Все почалося з того, що в пʼятницю в RabbitMQ почали зростати меседжі. «Ми просто не масштабували деякі з воркерів повʼязаних сервісів», — подумали ми та додали воркери і ресурси інфраструктури. Нам здалося, що проблема вирішена. Проте за ніч кількість меседжів різко зростає, і все падає. 


Що сталося: у нас є сервіс, який відповідає за нотифікації про зміни в системі для аналітики, процесингу на бекендах тощо. Проєктуючи нову систему, ми вирішили, що ця інформація дуже важлива, тому використовувати брокер повідомлень ризиковано. Ми згадали, що в Symfony Messenger є транспорт — Doctrine, отже вирішили скласти ці дані в базу і використовувати її як сторедж для воркера. Все працювало, але сталося так, що міграція збільшила навантаження настільки, що ми за 2 тижні згенерували в 7 разів більше івентів, ніж за попередні пів року в продакшені. 




Це призвело до зростання локів, а масштабування воркерів тільки погіршило ситуацію. Ми знайшли оптимальну кількість, щоби черга поступово розгрібалася, і в спокійному режимі перейшли з Doctrine на Rabbit як транспорт. Це вдалося зробити невеликими змінами в конфігах. Проблему було вирішено. 


Згодом міграцію було завершено, і ми перейшли в режим стабілізації. Чи можна говорити про Happy End? Загалом вважаю наш досвід позитивним: ми справилися, хоч і неідеально. Враховуючи, що для нас це був повний Black Box, результат можна вважати успішним. Адже все що нас не вбиває, додає рядок у CV.


  

Головні уроки


  1. Ставте за мету не створювати Legacy. Ми часто потрапляємо у пастку «давайте зараз зробимо швидко, а потім виправимо, як треба». Правда полягає в тому, що до цього «потім» майже ніколи не повертаєшся. 

  2. Множте естімейти. Коли працюєте з системою, яку не знаєте досконально, закладайте додатковий час на те, щоби в ній розібратися. У нашому випадку оптимістичний сценарій варто було множити у 2,5-3 рази. 

  3. Валідуйте технічні рішення з іншими інженерами. Колеги можуть оцінити вашу задачу свіжим поглядом та помітити потенційні ризики.

  4. Адаптуйте процеси. Іноді краще відмовитися від ідеальної архітектури та піти на компроміси. Наприклад, ми дозволили собі робити прямі конекти з одного сервісу до всіх необхідних баз. Це був одноразовий функціонал, і писати повноцінні API та тести до них зайняло б набагато більше часу. 

  5. Не робіть великі зміни в командах чи проєктах під час і одразу після такого проєкту. По завершенню міграції команда може мати певні завдання для допрацювання. Наприклад, у нас досі не завершені деякі тікети. Тому краще не тиснути на команду новими проєктами, а дати їй час.

© 2035 by Business Name. Made with Wix Studio™

bottom of page