High speed Yii : reduce database usage / уменьшение использования базы данных
Выключил опцию ‘autoConnect’ => false в настройках базы данных. Теперь при работе веб-приложения не должно создаваться соединение с базой данных, если она не требуется. Но при тестировании под нагрузкой регулярно появляется ошибка из MySQL «Too many connections». Исправил.
Можно взять из репозитория https://github.com/pvolyntsev/yii (v1.1.16-dev) или добавить заплатку в текущей версии.
Условия применения
Заплатка требуется для веб-приложений на базе Yii 1.x, имеющих повышенные нагрузки. При этом создаётся много процессов PHP, каждый из которых может создавать соединение с базой данных.
$command = Yii::app()->db
->createCommand("SELECT * FROM some_table LIMIT 10");
$result = $command->queryAll();
Результат запроса можно поместить в кэш и тогда при повторном запросе база данных не понадобится.
$command = Yii::app()->db
->cache(60) // сохранить результат запроса в кэш на 60 секунд
->createCommand("SELECT * FROM some_table LIMIT 10");
$result = $command->queryAll(); // запрос не выполнится, если данные найдены в кэше
Если процессу PHP потребуются только те данные, которые уже были сохранены в кэш, создавать соединение с базой данных не надо. Но оно создаётся.
Заплатка
Заплатка подавляет создание соединения при создании объекта CDbCommand.
<?php
/**
* DbConnection class file
*
* @author Pavel Volyntsev <pavel.volyntsev@gmail.com>
* @link http://copist.ru/
*/
Yii::import('application.components.db.DbCommand');
/**
* DbConnection represents a connection to a database.
*
* (Reduce database usage -- do not open connection if no database commands prepared and no transactions)
*/
class DbConnection extends CDbConnection
{
/**
* @see CDbConnection::createCommand
* @return DbCommand the DB command
*/
public function createCommand($query=null)
{
return new DbCommand($this,$query);
}
}
<?php
/**
* DbCommand class file
*
* @author Pavel Volyntsev <pavel.volyntsev@gmail.com>
* @link http://copist.ru/
*/
/**
* DbCommand represents an SQL statement to execute against a database.
*
* (reduce database usage: open connection to database before prepare of query)
*/
class DbCommand extends CDbCommand
{
/**
* @return CDbConnection the connection associated with this command
*/
public function getConnection()
{
$connection = parent::getConnection();
$connection->setActive(true);
return $connection;
}
}
Как проявляется действие заплатки?
$command = Yii::app()->db->cache(60)->createCommand("SELECT * FROM some_table LIMIT 10");
Если результат этого запроса уже есть в кэше, то соединение с базой не устанавливается.
На что влияет заплатка?
Если в коде было прямое использование объекта «соединение с базой данных», то этот объект теперь является NULL до тех пор, пока не выполнился хотя бы один запрос.
Как результат можно изменить соотношение «количество соединений с базой НА количество процессов PHP». Соотношение устанавливается опытным путём при проведении нагрузочного тестирования. Количество соединений задаётся в настройках MySQL, количество процессов PHP — через настройки PHP-FPM и NGINX.
Pull request
Отклонён по причине «Вackward Сompatibility break» — слишком много кода полагается на то, что в момент создания CDbCommand откроется соединение с базой.
Можете взять себе отдельный комит из моего репозитория.
Совместимость с существующим кодом приложения
Нужно будет тщательно проанализировать код существующего приложения, провести тестирование с использованием модульных тесты (unit test), поискать код, похожий по логике на указанные ниже сигнатуры.
$connection = new CDbConnection('sqlite::memory:');
$connection->autoConnect = false; // выключить автоматическое соединение с базой
// попытка получить прямой доступ к базе данных
$connection->pdoInstance->...; // эта конструкция не сработает, потому что объект pdoInstance ещё не определён
$connection = new CDbConnection('sqlite::memory:');
$connection->autoConnect = false; // выключить автоматическое соединение с базой
$command = $connection->createCommand(); // раньше метод createCommand() сразу же открывал соединение с базой данных
// попытка получить прямой доступ к базе данных
$connection->pdoInstance->...; // эта конструкция не сработает, потому что объект pdoInstance ещё не определён
$command->connection->pdoInstance->...; // эта конструкция тоже не сработает
Сигнатуры — это фрагменты кода, похожие на указанные выше, но не совпадающие дословно.