Оптимизация производительности

Существует много факторов, влияющих на производительность веб-приложения. Какие-то относятся к окружению, какие-то к вашему коду, а какие-то к самому Yii. В этом разделе мы перечислим большинство из них и объясним, как можно улучшить производительность приложения, регулируя эти факторы.

Оптимизация окружения PHP

Хорошо сконфигурированное окружение PHP очень важно. Для получения максимальной производительности,

  • Используйте последнюю стабильную версию PHP. Мажорные релизы PHP могут принести значительные улучшения производительности.
  • Включите кеширование байткода в Opcache (PHP 5.5 и старше) или APC (PHP 5.4 и более ранние версии). Кеширование байткода позволяет избежать затрат времени на обработку и подключение PHP скриптов при каждом входящем запросе.

Отключение режима отладки

При запуске приложения в производственном режиме, вам нужно отключить режим отладки. Yii использует значение константы YII_DEBUG чтобы указать, следует ли включить режим отладки. Когда режим отладки включен, Yii тратит дополнительное время, чтобы создать и записать отладочную информацию.

Вы можете разместить следующую строку кода в начале входного скрипта чтобы отключить режим отладки:

defined('YII_DEBUG') or define('YII_DEBUG', false);

Info: Значение по умолчанию для константы YII_DEBUGfalse. Так что, если вы уверены, что не изменяете значение по умолчанию где-то в коде приложения, можете просто удалить эту строку, чтобы отключить режим отладки.

Использование техник кеширования

Вы можете использовать различные техники кеширования чтобы значительно улучшить производительность вашего приложения. Например, если ваше приложение позволяет пользователям вводить текст в формате Markdown, вы можете закешировать разобранное содержимого Markdown, чтобы избежать разбора одной и той же разметки Markdown неоднократно при каждом запросе. Пожалуйста, обратитесь к разделу Кеширование чтобы узнать о поддержке кеширования, которую предоставляет Yii.

Включение кеширования схемы

Кэширование схемы - это специальный тип кеширования, который должен быть включен при использовании Active Record. Как вы знаете, Active Record достаточно умен, чтобы обнаружить информацию о схеме (например, имена столбцов, типы столбцов, ограничения) таблицы БД без необходимости описывать ее вручную. Active Record получает эту информацию, выполняя дополнительные SQL запросы. При включении кэширования схемы, полученная информация о схеме будет сохранена в кэше и повторно использована при последующих запросах.

Чтобы включить кеширование схемы, сконфигурируйте компонент приложения cache для хранения информации о схеме и установите yii\db\Connection::$enableSchemaCache в true в конфигурации приложения:

return [
    // ...
    'components' => [
        // ...
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=mydatabase',
            'username' => 'root',
            'password' => '',
            'enableSchemaCache' => true,

            // Продолжительность кеширования схемы.
            'schemaCacheDuration' => 3600,

            // Название компонента кеша, используемого для хранения информации о схеме
            'schemaCache' => 'cache',
        ],
    ],
];

Объединение и минимизация ресурсов

Сложные веб-страницы часто подключают много CSS и/или JavaScript файлов. Для уменьшения числа HTTP запросов и общего размера загрузки этих ресурсов, вы должны рассмотреть вопрос об их объединении в один файл и его сжатии. Это может сильно увеличить скорость загрузки страницы и снизить нагрузку на сервер. Для получения более подробной информации обратитесь, пожалуйста, к разделу Ресурсы

Оптимизация хранилища сессий

По умолчанию данные сессий хранятся в файлах. Это удобно для разработки или в маленьких проектах. Но когда дело доходит до обработки множества параллельных запросов, то лучше использовать более сложные хранилища, такие как базы данных. Yii поддерживает различные хранилища "из коробки". Вы можете использовать эти хранилища, сконфигурировав компонент session в конфигурации приложения как показано ниже,

return [
    // ...
    'components' => [
        'session' => [
            'class' => 'yii\web\DbSession',

            // Установите следующее, если вы хотите использовать компонент БД, с названием
            // отличным от значения по умолчанию 'db'.
            // 'db' => 'mydb',

            // Чтобы перезаписать таблицу сессий, заданную по умолчанию, установите
            // 'sessionTable' => 'my_session',
        ],
    ],
];

Приведенная выше конфигурация использует таблицу базы данных для хранения сессионных данных. По умолчанию, используется компонент приложения db для подключения к базе данных и сохранения сессионных данных в таблице session. Вам нужно будет создать таблицу session заранее:

CREATE TABLE session (
    id CHAR(40) NOT NULL PRIMARY KEY,
    expire INTEGER,
    data BLOB
)

Вы также можете хранить сессионные данные в кеше с помощью yii\web\CacheSession. Теоретически, вы можете использовать любое поддерживаемое хранилище кеша. Тем не менее, помните, что некоторые хранилища кеша могут сбрасывать закешированные данные при достижении лимитов хранилища. По этой причине, вы должны в основном использовать хранилища кеша, которые не имеют таких лимитов.

Если на вашем сервере установлен Redis, настоятельно рекомендуется выбрать его в качестве хранилища сессий используя yii\redis\Session.

Оптимизация базы данных

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

Общей методикой для повышения производительности запросов к БД является создание индексов для тех столбцов таблицы, по которым делается выборка. Например, если вам нужно найти запись о пользователе по username, вам надо создать индекс на username. Обратите внимание, что в то время как индексирование может сделать SELECT запросы намного быстрее, оно будет замедлять INSERT, UPDATE и DELETE запросы.

Для сложных запросов к БД рекомендуется создавать представления базы данных (views), чтобы сэкономить время подготовки и разбора запросов.

Последнее, хотя и не менее важное: используйте LIMIT в ваших SELECT запросах. Это позволяет избежать извлечения большого количество данных из базы данных и исчерпания памяти, выделенной для PHP.

Использование обычных массивов

Хотя Active Record очень удобно использовать, это не так эффективно, как использование простых массивов, когда вам нужно получить большое количество данных из БД. В этом случае вы можете вызвать asArray() при использовании Active Record для получения данных, чтобы извлеченные данные были представлены в виде массивов вместо громоздких записей Active Record. Например,

class PostController extends Controller
{
    public function actionIndex()
    {
        $posts = Post::find()->limit(100)->asArray()->all();
        
        return $this->render('index', ['posts' => $posts]);
    }
}

В приведенном выше коде, $posts будет заполнего массивом строк из таблицы. Каждая строка - это обычный массив. Чтобы получить доступ к столбцу title в i-й строке, вы можете использовать выражение $posts[$i]['title'].

Вы также можете использовать DAO для создания запросов и извлечения данных в виде обычных массивов.

Оптимизация автозагрузчика Composer

Поскольку автозагрузчик Composer'а используется для подключения большого количества файлов сторонних классов, вы должны оптимизировать его, выполнив следующую команду:

composer dumpautoload -o

Асинхронная обработка данных

Когда запрос включает в себя некоторые ресурсоёмкие операции, вы должны подумать о том, чтобы выполнить эти операции асинхронно, не заставляя пользователя ожидать их окончания.

Существует два метода асинхронной обработки данных: pull и push.

В методе pull, всякий раз, когда запрос включает в себя некоторые сложные операции, вы создаете задачу и сохраняете ее в постоянном хранилище, таком как база данных. Затем в отдельном процессе (таком как задание cron) получаете эту задачу и обрабатываете ее.

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

В методе push, вы можете использовать очереди сообщений (например, RabbitMQ, ActiveMQ, Amazon SQS, и т.д.) для управления задачами. Всякий раз, когда новая задача попадает в очередь, это инициирует обработку этой задачи обработчиком.

Профилирование производительности

Вы должны профилировать код, чтобы определить узкие места в производительности и принять соответствующие меры. Следующие инструменты для профилирования могут оказаться полезными: