Тонкости настройки Yii под высокие нагрузки

в 23:03, , рубрики: php, yii, yii framework, метки: ,

Здравствуй.

После анонса своего проекта на Хабре, группа трудящихся в комментариях изъявила желание узнать более подробно, о технологиях используемых в проекте и тонкостях настроек. Как я писал ранее в проекте используется Yii фреймворк, о настройках которого и поговорим.

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

Альтернативный PHP кэш

Самым легким, быстрым и простым способом увеличить производительность Yii приложения является включение Альтернативного PHP кэша (Alternative PHP Cache). Его легко можно установить из PECL (PHP Extension Community Library). Я использую Linux и поэтому дальше команды будут именно для него.

pecl install apc

pecl инсталлятор сам должен прописать путь до библиотеки в конфигурационном файле php (php.ini). Но на всякий случай для работы расширения в файле должна быть следующая строка:

extension=apc.so

Библиотека APC предоставляет замечательный файл (apc.php) в котором можно видеть состояние кэша, количество памяти отведенной под кэш, значение которые в данный момент присвоены переменным отвечающим за настройки кэша.

Данный файл имеет название apc.php и если вы устанавливали его используя PECL, то он должен лежать в данной папке. /usr/share/php-pecl-apc/apc.php Данный файл необходимо перенести в корень вашего веб сервера, в целях безопасности предварительно поменяв название и установив в настройках файла логин и пароль.

Вот строка которую нужно изменить для установки пароля:

defaults('ADMIN_USERNAME','apc');                     // Admin Username
defaults('ADMIN_PASSWORD','password');    // Admin Password - CHANGE THIS TO ENABLE!!!
 

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

Замена зугрузчика с Yii.php на Yiilite.php

Для полноценной работы кэша промежуточного кода PHP, создатели фреймворка рекомендуют вместо загрузчика по умолчанию yii.php использовать более легкий yiilite.php. В чем все таки разница, в файл yiilite.php разработчики фреймворка включили часто используемые классы и вместе с этим убрали все комментарии и выражения трассировки, поэтому использование yiilite.php уменьшает количество подключаемых файлов и выполняемого кода.

Заменить загрузчик yii.php на yiilite.php можно в файле index.php который находится в корне вашего Yii проекта.

Меняем данную строку:

$yii=dirname(__FILE__).'/../путь к Yii/framework/yii.php';

На:

$yii=dirname(__FILE__).'/../путь к Yii/framework/yiilite.php';

Во многих источниках и на официальном форуме Yii в частности не раз отмечалось, что даже при использовании APC и yiilite.php на некоторых конфигурациях производительность не растет, а падает, так же не рекомендуется использовать yiilite.php без APC кэша, или любой другой технологии кэширования промежуточного кода, таких как eAccelerator или ZendOptimizer. В любом случае данные настройки требуют глубоко тестирования перед запуском их на боевых серверах.

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

Естественно что на боевых серверах нам нужно отключить режим отладки в Yii это делается по средствам удаления или коментирования строки, определяющую константу YII_DEBUG или просто можно присвоить ей значение false. Сделать это можно в файле index.php который находится в корне вашего Yii проекта.

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

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

Выбор инструментария для работы с базой данных:

Все мы знаем, что начинать оптимизацию производительности приложения следует с запросов к базе данных, это почти всегда самое узкое место. При создании своего первого тестового приложения Yii использует в коде для запросов к базе данных ActiveRecord. Вещь безусловно хорошая, но и в большинстве случаев медленная. Если вы все таки решили пожертвовать производительностью ради удобства и используете ActiveRecord, разработчики Yii рекомендуют включить кэширование структуры базы данных. Сделать это можно в главном конфигурационном файле фреймворка (/корень вашего Yii проекта/protected/config/main.php):

установить значение переменной schemaCachingDuration в значение больше нуля.

	// application components
	'components'=>array(
		'db'=>array(
			'connectionString' => '',
			'username' => '',
			'password' => '',
			'charset' => 'utf8',
                        'schemaCachingDuration' => 1000,
		),
	),

Значение 1000 означает, что полученные данные схемы базы данных могут оставаться валидными в кэше в течении 1000 секунд.

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

Вот эта конструкция на DAO будет работать быстрее, но кода чуть больше, хотя и можно записать все в одну строку(показано чуть ниже).

$query = 'select fruit_name from fruit_table where fruit_price = :price_value';

$command =Yii::app()->db->createCommand($query);

$command->bindValue(":price_value", 200, PDO::PARAM_INT);

$result_array = $command->queryAll();

Тоже самое в одну строку:

$result_array = Yii::app()->db->createCommand($query)->bindValue(":price_value", 200, PDO::PARAM_INT)->queryAll();

К слову сказать в своем проекте я использую только DAO и очень доволен, что перешел на него сразу, так
как просто видел какие запросы генерирует Active Record при начальном тестировании, так что я выбрал больший контроль над SQL запросами нежели удобство.

Подробнее о параметрах DAO всегда можно прочитать тут

На ActiveRecord то же действие будет выглядеть примерно так, кода чуть меньше, скорость ниже и контроля над SQL запросами практически нет:

$result_object = fruit_table::model()->findAllByAttributes(array("fruit_price"=>$price_value));

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

Кэширование запросов к базе данных:

Кэширование запросов так же может сильно снизить нагрузку на базу данных. Перед тем как писать запросы использующие кэширование нужно удостоверится что кэширование запросов включено и поддерживается используемой вами версией фреймворка. Yii стал поддерживать кеширование запросов начиная с версии 1.1.7 свойство класса CDbConnection отвечающее за включение кэша и за компонент реализующий кеширование называется queryCacheID по умолчанию оно имеет значение cache. Так что кеширование запросов должно работать «из коробки» если ваша версия фреймворка его поддерживает.

Вот пример простого кэшированного запроса для DAO.


$query = 'select * from fruit_table limit 10';

$result = Yii::app()->db->cache(1000)->createCommand($query)->queryAll(); 

Давайте разберем подробнее параметры метода cache.


cache(integer $duration, CCacheDependency $dependency=NULL, integer $queryCount=1)

$duration — значение в секундах используемое для проверки того что данные в кэше не устарели, к примеру если при выполнении запроса прошло менее 1000 секунд
со времени последней записи в кэш, то будут использованы данные из кэша, если прошло больше 1000 секунд, то снова будет выполнен запрос и новые данные будут записаны в кеш.

$dependency — зависимости по которым определяется валидность кеша. Для лучшего понимания данного параметра я попытался чуть-чуть изменить предыдущий наш код.

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


$query = 'select * from fruit_table limit 10';

$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM fruit_table');

$result = Yii::app()->db->cache(1000, $dependency)->createCommand($query)->queryAll(); 

При выполнении выше написанного кода, Yii проверяет, есть ли в кэше данные с запросом в качестве индекса, не устарели ли эти данные ( в нашем случае прошло менее 1000 секунд($duration) с последней записи в кэш), максимальное значение update_time идентично тому значению которое было зафиксировано при сохранении результата запроса в кэш ($dependency)

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

Последним параметром метода cache является
$queryCount – он контролирует число запросов, при исполнении которых будет использоваться кэш.

Я активно использую кэширование запросов к базе данных в своем приложении в Yii оно реализовано просто замечательно и работает замечательно позволяя экономить память и процессорное время лишний раз не шерстя базу данных.

Кэширование фрагментов страниц

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

В Yii с этим все просто выбираем уникальный идентификатор для каждой части страницы, которую собираемся кэшировать и обрамляем содержимое вот такой конструкцией:

<?php if($this->beginCache($id)) { ?>
---Фрагмент страницы который хотим закэшировать---
<?php $this->endCache(); } ?>

Более подробно о кэшировании частей страниц можно узнать в разделе официального руководства по Yii вот тут.

Так же Yii позволяет кэшировать и целые страницы, но в моем проекте данный функционал не используется. Об этом можно почитать здесь.

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

Кэширование. Хранилища кэшируемых данных

Кэширование. Валидность кэша, зависимости

Кэширование. Что, чем и как

P.S.

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

Автор: Solovej


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js