Upgrade to Pro — share decks privately, control downloads, hide ads and more …

«Тактика распила PHP-монолита» — Лакосников Павел, Авито

Badoo Tech
February 15, 2020

«Тактика распила PHP-монолита» — Лакосников Павел, Авито

«Последние три года мы в Авито активно разделяем PHP-монолит на микросервисы. В процессе нашли много устаревших продуктовых и технологических решений — неактуальные хранилища, слишком «толстые» ответы, неиспользуемые методы API.

В докладе расскажу, как мы избавлялись от легаси:‌ выносили словари и другую статику, выделяли интерфейсы, упрощали иерархию наследования и совершенствовали покрытие тестами. А ещё — как улучшаем то, что пока осталось в монолите».

Badoo Tech

February 15, 2020
Tweet

More Decks by Badoo Tech

Other Decks in Programming

Transcript

  1. Павел Лакосников
    Senior engineer,
    юнит Antimonolith
    Тактика распила
    монолита

    View Slide

  2. !2
    Из монолита в микросервисы

    View Slide

  3. Монолит 3 года
    назад
    !3

    View Slide

  4. !4
    Как выглядел монолит 3 года назад:
    2.6к файлов и 200к строк PHP
    1.5к html файлов
    1к+ файлов js, lua, python, perl
    ServiceRegistry + Global как основные способ
    работы с базой
    Разветвлённая многоуровневая система
    наследования

    View Slide

  5. !5
    Как выглядел монолит 3 года назад:
    2.6к файлов и 200к строк PHP
    1.5к html файлов
    1к+ файлов js, lua, python, perl
    ServiceRegistry + Global как основные способ
    работы с базой
    Разветвлённая многоуровневая система
    наследования

    View Slide

  6. Он работал!
    !6

    View Slide

  7. !7
    Это был
    местами
    читаемый и
    неплохой код

    View Slide

  8. !8
    Он был
    надёжен

    View Slide

  9. !9
    Он был
    надёжен,

    если работал

    View Slide

  10. !10
    Он работал
    очень быстро

    View Slide

  11. !11
    Чем же он нам
    мешал?

    View Slide

  12. !12
    Не позволял быть гибкими

    View Slide

  13. !13
    Не позволял быть гибкими
    Не позволял командам быстро внедрять
    изменения, не вызывая «эффект лавины»

    View Slide

  14. !14
    Не позволял быть гибкими
    Не позволял командам быстро внедрять
    изменения, не вызывая «эффект лавины»
    Не позволял экспериментировать

    View Slide

  15. !15
    Не позволял быть гибкими
    Не позволял командам быстро внедрять
    изменения, не вызывая «эффект лавины»
    Не позволял экспериментировать
    Гигантскими тяжелыми моделями

    View Slide

  16. !16
    Начинаем
    распиливать…

    View Slide

  17. Вендоризация
    !17

    View Slide

  18. !18
    Как выглядел
    код в монолите

    View Slide

  19. !19
    Пример сессий: Роутеры

    View Slide

  20. !20
    Пример сессий: Бэкофис

    View Slide

  21. !21
    Пример сессий: Авторизация

    View Slide

  22. !22
    Пример сессий: Мессенджер

    View Slide

  23. !23
    Пример сессий: Наследники

    View Slide

  24. !24
    Вендоризация
    1. Собираем код в отдельный пакет
    2. Прогоняем статический анализатор
    3. Пишем тесты
    4. Подключаем к монолиту как внешнюю
    библиотеку

    View Slide

  25. !25
    Зачем?
    1. Контроль за изменениями
    2. Изоляция всех внешних зависимостей
    3. Одинаковое поведение у всех
    потребителей

    View Slide

  26. !26
    Как не надо
    делать

    View Slide

  27. !27
    Не писать тесты
    на перенесённый
    код

    View Slide

  28. !28
    Просто
    копировать
    файлики

    View Slide

  29. !29
    Переносить все в
    одну библиотеку

    View Slide

  30. Вынос словарей
    !30

    View Slide

  31. !31
    Что именно
    Список городов
    Список районов
    Список категорий
    Список микрокатегорий

    View Slide

  32. !32
    Зачем?
    Это корневые зависимости, нужные не
    только в монолите, но и в большинстве
    сервисов

    View Slide

  33. !33
    Зачем?
    Это корневые зависимости, нужные не
    только в монолите, но и в большинстве
    сервисов
    Фиксация констант, интерфейсов

    View Slide

  34. !34
    Зачем?
    Это корневые зависимости, нужные не
    только в монолите, но и в большинстве
    сервисов
    Фиксация констант, интерфейсов
    Контроль за их изменением

    View Slide

  35. !35
    Зачем?
    Это корневые зависимости, нужные не
    только в монолите, но и в большинстве
    сервисов
    Фиксация констант, интерфейсов
    Контроль за их изменением
    Доступность для любых сервисов

    View Slide

  36. !36
    Как именно?
    Вендоризовали интерфейсы всех словарных
    сущностей, все константы

    View Slide

  37. !37
    Как именно?
    Вендоризовали интерфейсы всех словарных
    сущностей, все константы
    Отделили базу данных, хранящих словарные
    сущности

    View Slide

  38. !38
    Как именно?
    Вендоризовали интерфейсы всех словарных
    сущностей, все константы
    Отделили базу данных, хранящих словарные
    сущности
    Сделали микросервис, способный отдавать
    словарные данные в куче форматов

    View Slide

  39. !39
    Как именно?
    Вендоризовали интерфейсы всех словарных
    сущностей, все константы
    Отделили базу данных, хранящих словарные
    сущности
    Сделали микросервис, способный отдавать
    словарные данные в куче форматов
    Доработали сборку монолита, добавив этап
    сборки словарей

    View Slide

  40. !40

    View Slide

  41. Уменьшение глубины
    наследования
    !41

    View Slide

  42. !42
    Глубина
    наследования
    классов достигала 8
    уровней

    View Slide

  43. !43
    Controller_Site_Profile_Settings_Ru

    View Slide

  44. !44
    Расширение ответа
    по цепочке от
    наследников к
    родителю

    View Slide

  45. !45
    На вход в метод отрисовки
    объявления
    $expandedItem = [
    'id' => $itemId,
    'category' => [
    'id' => $itemCategory->id,
    'name' => $itemCategory->name
    ]
    ];

    View Slide

  46. !46
    На выходе из цепочки
    6 килобайт полезных данных
    150 различных полей

    View Slide

  47. !47
    В самом конце

    if ($item->isOnModerate()) {
    return $this->notFound();
    }
    ];

    View Slide

  48. !48
    Проблемы
    Трудно рефакторить
    Легко задеть чужую функциональность
    Избыточность данных

    View Slide

  49. !49
    Что вообще происходит?

    View Slide

  50. !50
    Как именно?
    Диаграммы вызовов в разрезе запроса

    View Slide

  51. !51
    Как именно?
    Диаграммы вызовов в разрезе запроса
    Вдумчивый и внимательный рефакторинг

    View Slide

  52. !52
    NO SILVER BULLET

    View Slide

  53. Классы вместо
    массивов
    !53

    View Slide

  54. !54
    Что именно?
    Многие сущности представлялись в коде как
    большие массивы с данными

    View Slide

  55. !55
    Что именно?
    Многие сущности представлялись в коде как
    большие массивы с данными
    Эти массивы передаются из метода в метод,
    по цепочке

    View Slide

  56. !56
    Что именно?
    Многие сущности представлялись в коде как
    большие массивы с данными
    Эти массивы передаются из метода в метод,
    по цепочке
    В процессе дополняются, расширяются и
    декорируют

    View Slide

  57. !57
    Зачем?
    Больше типизации => меньше ошибок

    View Slide

  58. !58
    Зачем?
    Больше типизации => меньше ошибок
    Понимание, какие поля и где используются

    View Slide

  59. !59
    Зачем?
    Больше типизации => меньше ошибок
    Понимание, какие поля и где используются
    Удаление неиспользуемых или устаревших
    полей

    View Slide

  60. !60
    Как именно?
    Создаем простейшую модель Item {id, title}

    View Slide

  61. !61
    Как именно?
    Создаем простейшую модель Item {id, title}

    View Slide

  62. !62
    Как именно?
    Постепенно расширяем
    её, создавая новые типы
    данных (Price, Contacts) и
    сокращая объем $data

    View Slide

  63. Сделали маленькие
    модели
    !63

    View Slide

  64. !64
    Что именно?
    На прошлом этапе у нас получились большие
    модели

    View Slide

  65. !65
    Объявление в поиске

    View Slide

  66. !66
    Страница объявления

    View Slide

  67. !67
    Объявление в чужом профиле

    View Slide

  68. !68
    Объявление в своём профиле

    View Slide

  69. !69
    Изначально везде
    была одна модель

    View Slide

  70. !70
    Проблемы
    Огромная холостая нагрузка на сеть

    View Slide

  71. !71
    Проблемы
    Огромная холостая нагрузка на сеть
    Низкая надежность

    View Slide

  72. !72
    Проблемы
    Огромная холостая нагрузка на сеть
    Низкая надежность
    Сложность тестирования

    View Slide

  73. !73
    Проблемы
    Огромная холостая нагрузка на сеть
    Низкая надежность
    Сложность тестирования
    Невозможность создать микросервис,
    отвечающий только за часть свойств этой
    модели

    View Slide

  74. !74
    Как именно?
    Выделили десятки интерфейсов из большой
    модели

    View Slide

  75. !75
    Как именно?
    Выделили десятки интерфейсов из большой
    модели
    Разбили одну большую модель на множество
    маленьких

    View Slide

  76. !76
    Как именно?
    Выделили десятки интерфейсов из большой
    модели
    Разбили одну большую модель на множество
    маленьких
    Сделали отдельные фабрики для каждой из
    них

    View Slide

  77. Перешли на
    DataMappper вместо
    ORM
    !77

    View Slide

  78. !78
    Что именно?
    Вызовы хранимок на получение данных из
    самых разных места монолита

    View Slide

  79. !79
    Что именно?
    Вызовы хранимок на получение данных из
    самых разных места монолита
    Сохранение моделей в отличном от создания
    месте

    View Slide

  80. !80
    Что именно?
    Вызовы хранимок на получение данных из
    самых разных места монолита
    Сохранение моделей в отличном от создания
    месте
    Разные алиасы у полей, и способы получения
    данных

    View Slide

  81. !81
    Достаем item[100] из базы

    View Slide

  82. !82
    Достаем item[100] из базы

    View Slide

  83. !83
    Достаем item[100] из базы

    View Slide

  84. !84
    Проблемы
    Большая глубина наследования на классах
    занимающихся работой с моделью

    View Slide

  85. !85
    Проблемы
    Большая глубина наследования на классах,
    занимающихся работой с моделью
    Большое количество входных похожих точек,
    вызывающих работу с моделью

    View Slide

  86. !86
    Проблемы
    Большая глубина наследования на классах
    занимающихся работой с моделью
    Большое количество входных похожих точек,
    вызывающих работу с моделью
    Неконсистентность вызовов

    View Slide

  87. !87
    Как именно?
    Переход на dataMapper модель

    View Slide

  88. !88
    Как именно?
    Переход на dataMapper модель
    Подстройка публичного интерфейса под
    нужды конкретных потребителей

    View Slide

  89. !89
    Как именно?
    Переход на dataMapper модель
    Подстройка публичного интерфейса под
    нужды конкретных потребителей
    Инкапсулюяция всей логики базы связанной
    с сущностью в одном месте

    View Slide

  90. Отказались от
    поддержки старых
    приложений
    !90

    View Slide

  91. !91
    Что именно?
    Большой хвост из старых версий мобильных
    приложений

    View Slide

  92. !92
    Что именно?
    Большой хвост из старых версий мобильных
    приложений
    Большое десктоп-апи, которые не чистили
    раньше

    View Slide

  93. !93
    Проблемы
    Старые методы ожидали очень большие
    модели данных, избыточные для их работы

    View Slide

  94. !94
    Проблемы
    Старые методы ожидали очень большие
    модели данных, избыточные для их работы
    Часть запросов использовались
    исключительно ботами

    View Slide

  95. !95
    Проблемы
    Старые методы ожидали очень большие
    модели данных, избыточные для их работы
    Часть запросов использовались
    исключительно ботами
    Часть запросов были «мертвыми»

    View Slide

  96. !96
    Проблемы
    Старые методы ожидали очень большие
    модели данных, избыточные для их работы
    Часть запросов использовались
    исключительно ботами
    Часть запросов были «мертвыми»
    Осложняли рефакторинг

    View Slide

  97. !97
    Как именно?
    Сделали метрики по использованию API

    View Slide

  98. !98

    View Slide

  99. !99
    Как именно?
    Сделали метрики по использованию API
    Сверились c ними

    View Slide

  100. !100

    View Slide

  101. !101
    Как именно?
    Сделали метрики по использованию API
    Сверились c ними
    Отказались от поддержки версий
    приложений с минорным трафиком

    View Slide

  102. !102
    Начали делать
    это регулярно

    View Slide

  103. Перешли на Redis
    !103

    View Slide

  104. !104
    Что именно?
    В проекте было несколько коммунальных
    мемкешей
    Использовались они для кратковременного
    кеширования

    View Slide

  105. !105
    Проблемы
    Перетирали ключи в разных потребителях

    View Slide

  106. !106
    Проблемы
    Перетирали ключи в разных потребителях
    Автоматически конвертит классы через
    php_serialize (круто, когда класс
    переименовали а кэш нет)

    View Slide

  107. !107
    Проблемы
    Перетирали ключи в разных потребителях
    Автоматически конвертит классы через
    php_serialize (круто, когда класс
    переименовали а кэш нет)
    Не перистентен

    View Slide

  108. !108
    Проблемы
    Перетирали ключи в разных потребителях
    Автоматически конвертит классы через
    php_serialize (круто, когда класс
    переименовали а кэш нет)
    Не перистентен
    Общие политики вытеснения для всех

    View Slide

  109. !109
    Как именно?
    Плавно заменили несколько мемкешей на
    большую пачку персистентных Redis

    View Slide

  110. !110
    Как именно?
    Плавно заменили несколько мемкешей на
    большую пачку персистентных Redis
    Выделили отдельные обертки под отдельные
    Redis

    View Slide

  111. !111
    Как именно?
    Плавно заменили несколько мемкешей на
    большую пачку персистентных Redis
    Выделили отдельные обертки под отдельные
    Redis
    Разделили потребителей каждый по своему
    Redis’у

    View Slide

  112. !112
    Их еще и из
    монолита легче
    вытащит

    View Slide

  113. Что же стало с
    монолитом?
    !113

    View Slide

  114. !114
    Монолит не распилен

    View Slide

  115. !115
    Он даже
    увеличился

    View Slide

  116. !116
    13к файлов PHP
    554к строк PHP

    View Slide

  117. !117
    ≈ 300микросервисов
    ≈ 1500 репозиториев

    View Slide

  118. !118
    Перешли на 

    PHP 7.4

    View Slide

  119. !119
    Что
    изменилось

    View Slide

  120. !120
    implode (string $glue , array $pieces )
    Раньше порядок аргументов не имел значения
    Было
    implode (string $glue, array $pieces): string
    implode (array $pieces, string $glue): string
    Стало
    implode (string $glue, array $pieces): string

    View Slide

  121. !121
    Внедрили DI

    View Slide

  122. !122
    Добавили литеры
    Php-stan
    Php-cs
    Route-checker
    Сrontab-checker
    Проверка схемы ответов API
    Линтер на examples ответов
    API
    Валидация контраков

    View Slide

  123. !123
    Добавили литеры
    Php-stan
    Php-cs
    Route-checker
    Сrontab-checker
    Проверка схемы ответов API
    Линтер на examples ответов
    API
    Валидация контраков

    View Slide

  124. !124
    У нас появился
    service-price-formatter

    View Slide

  125. !125
    Сервис форматирующий цену

    View Slide

  126. !126
    Да, это stateless сервис, просто
    форматирующий цену

    View Slide

  127. !127
    Он утилизирует 40 ядер

    View Slide

  128. !128
    Мы его удалили

    View Slide

  129. Декомпозируй
    правильно!
    !129

    View Slide

  130. Почти конец
    !130

    View Slide

  131. !131
    Чтобы распилить монолит…

    View Slide

  132. !132
    Не делайте
    price-formatter

    View Slide

  133. !133
    Избавьтесь от
    Dependency Hell

    View Slide

  134. !134
    Увеличьте
    покрытие
    тестами и
    литерами

    View Slide

  135. !135
    Откажитесь от
    ORM, 

    ServiceRegistry и
    Global’ов

    View Slide

  136. Павел Лакосников
    Senior engineer, юнит
    Antimonolith facebook: https://www.facebook.com/shaman.s.bubnom
    telegram: shaman_s_bubom

    View Slide