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

return array(
 	// ...
 	'components'=>array(
		// ...

		// Настройки для базы данных
		'db' => array(
 			'class' => 'application.components.db.DbConnection', // другой класс для работы с базой данных
			// ...
			'autoConnect' => false, // не устанавливать соединение при старте приложения
		),

		// ...
	),
	// ...
);

 

<?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->...; // эта конструкция тоже не сработает

Сигнатуры — это фрагменты кода, похожие на указанные выше, но не совпадающие дословно.

Павел Волынцев

Уже более 15 лет занимаюсь разработкой веб-проектов. Fullstack Senior Developer. IT евангелист — доношу свет знаний об информационных технологиях. Профессиональные цели: Дать людям возможность дать людям больше.

Читайте также: