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->...; // эта конструкция тоже не сработает
Сигнатуры — это фрагменты кода, похожие на указанные выше, но не совпадающие дословно.