Оптимизация Laravel-приложения | OTUS
⚡ Подписка на курсы OTUS!
Интенсивная прокачка навыков для IT-специалистов!
Подробнее

Курсы

Программирование
Python Developer. Professional
-3%
Разработчик на Spring Framework
-5%
iOS Developer. Professional
-8%
Golang Developer. Professional
-6%
Базы данных
-12%
Agile Project Manager
-5%
Android Developer. Professional
-11%
Microservice Architecture
-5%
C++ Developer. Professional
-5%
Highload Architect
-6%
JavaScript Developer. Basic
-8%
Backend-разработчик на PHP
-9%
C# Developer. Professional
-9%
Team Lead
-6%
Алгоритмы и структуры данных Разработчик программных роботов (RPA) на базе UiPath и PIX Unity Game Developer. Basic Разработчик голосовых ассистентов и чат-ботов Vue.js разработчик VOIP инженер NoSQL Супер-практикум по использованию и настройке GIT Symfony Framework iOS Developer. Basic Супер-интенсив «СУБД в высоконагруженных системах» Супер-интенсив "Tarantool"
Инфраструктура
DevOps практики и инструменты
-12%
Базы данных
-12%
Network engineer. Basic
-10%
Network engineer
-4%
Экcпресс-курс «ELK»
-10%
Инфраструктурная платформа на основе Kubernetes
-6%
Administrator Linux.Basic
-10%
Экспресс-курс «CI/CD или Непрерывная поставка с Docker и Kubernetes»
-30%
Дизайн сетей ЦОД
-13%
PostgreSQL
-8%
Разработчик программных роботов (RPA) на базе UiPath и PIX Reverse-Engineering. Professional Внедрение и работа в DevSecOps Administrator Linux. Advanced Infrastructure as a code in Ansible Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Экспресс-курс «IaC Ansible»
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

Оптимизация Laravel-приложения

Laravel_Deep_2.4-5020-6f82e4.png

Хочу поделиться небольшим кейсом оптимизации Laravel-приложения. Этот кейс служит хорошей иллюстрацией алгоритма оптимизации в целом, в процессе пришлось столкнуться с типичными проблемами, плюс он содержит несколько решений, относящихся именно к Laravel.

Исходно имеем микросервис авторизации, владеющий информацией о правах пользователей, реализованный на Laravel. Правами пользователи наделяются через роли (RBAC), система прав выглядит следующим образом: — трёхуровневое дерево; — на верхнем уровне — сервисы; — посередине — страницы; — на нижнем уровне — элементы страниц.

Для страниц и элементов страниц есть набор действий, которые с ними можно осуществлять. В тот момент времени, когда было решено заняться оптимизацией, сервис тратил на ответ о правах пользователя в рамках сервиса ~140 мс, что позволяло выдерживать только стандартную нагрузку и совсем не позволяло пиковую. Для удержания пиковой нагрузки без апгрейда сервера необходимо было снизить время ответа хотя бы до 50 мс. Целевым показателем было выбрано 20 мс, чтобы иметь запас по производительности.

Выполненные шаги по оптимизации

  1. Анализ запросов к БД (отказ от Eloquent и доменной модели прав). Поскольку данные о правах представлены иерархической структурой, но хранятся в реляционной БД (PostgreSQL), то первое предположение было о том, что основные «тормоза» происходят на этапе работы с БД. Анализ показал, что Eloquent выполнял O(N) запросов, где N – количество сервисов. Для получения набора прав было решено отказаться от Eloquent и доменной модели прав. Запросы переписаны на «сырой» SQL, в результате их количество сокращено до 3 (по одному для каждого уровня), плюс проводится дополнительная обработка результатов в коде. В результате время подготовки ответа удалось сократить до ~100 мс.
  2. Денормализация данных. Принято решение денормализовать данные и хранить в БД уже подготовленный ответ с полным набором прав пользователя по всем сервисам уже в JSON-формате, что позволит не тратить время на обработку и сократит время выборки. Как результат, удалось сократить ещё 2 запроса к БД и время подготовки ответа стало ~70 мс.
  3. Проблема обновления данных. После денормализации было установлено, что существует юзеркейс, в котором пользователям несколько раз меняется набор прав, после чего они тут же запрашиваются. Денормализация привела к тому, что время обновления прав существенно возросло, и в этом кейсе время подготовки ответа пользователю вернулось почти к исходному показателю (стало ~130 мс). Кейс не был очень существенным, однако подвёл к мысли, что решение пока неоптимально. В качестве дополнительной оптимизации изменили алгоритм денормализации и стали хранить данные по правам на каждый сервис в отдельном поле и отдавать информацию о правах на каждый сервис по отдельности (бизнес-логику это не нарушило, т. к. каждому сервису интересны права только про его страницы и элементы). Это позволило обновлять данные частично и немного уменьшило трафик, в итоге время подготовки типичного ответа стало ~60 мс, а в рассмотренном юзеркейсе ~90 мс.
  4. Выносим данные в кэш. Денормализованные данные стало возможно вынести в кэш, т. к. теперь обновление прав затрагивало конкретные кэшированные данные, которые можно было легко инвалидировать. Это позволило сократить время подготовки типичного ответа до ~50 мс. Пиковую нагрузку уже стало можно выдерживать, но без какого-либо запаса прочности.
  5. Оптимизируем работу IoC-контейнера (отказ от автоматического DI). Дальнейшая оптимизация со стороны источников данных уже не могла дать значительного прироста производительности, поэтому начали оптимизировать уже работу кода. В процессе анализа выяснилось, что автоматический подбор классов, реализующих требуемые интерфейсы в IoC-контейнере, достаточно медленный. Было решено отказаться от этого механизма и подсказывать контейнеру явно конкретные классы без привязки к интерфейсам. Это позволило сократить время до ~45 мс.
  6. Делаем часть работы IoC-контейнера вручную. Следующим шагом стал полный отказ от использования контейнера для инстанцирования отдельных сущностей, которые использовались для подготовки ответа с набором прав пользователя. Т. е. в провайдере служб эти сущности собирались вручную с lazy-инициализацией. Смогли выиграть ещё немного и получили результат ~37 мс.
  7. Оптимизация роутинга. Сервис поддерживал порядка 60 различных запросов, что приводило к довольно медленному роутингу. Часть запросов была удалена (в них возвращалась частично информация о правах, теперь стал использоваться полный вариант), часть объединена с переходом на дополнительные параметры в теле запроса. В результате удалось сократить количество запросов до ~20. Эта оптимизация плюс включение кэширования в провайдере роутинга позволило сократить время до ~30 мс (включение кэширования без оптимизации давало результат ~33 мс).

На этом этапе было принято решение остановиться, т. к. дальнейшая оптимизация уже требовала значительной переработки архитектуры. В итоге удалось сократить время на подготовку запроса почти в 4.7 раза и добиться некоторого запаса производительности даже на период пиковых нагрузок. Laravel-специфичная оптимизация на последних этапах позволила получить примерно треть итогового прироста производительности.

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться