Yii — php-фреймворк / [Из песочницы] Быстрые счетчики в Yii

в 8:28, , рубрики: cactiverecord, yii, метки: ,

В релизе 1.1.8 PHP-фреймворка Yii появилcя дополнительный метод в классе CActiveRecord для работы со счетчиками, а именно saveCounters. Наверное, многие из вас читали о нем в анонсе релиза. Под катом я расскажу в чем его крутость и почему стоит его использовать. Возможно, после прочтения вы побежите рефакторить свой код.
Прочитав в анонсе о появлении saveCounters я подумал, что это просто обертка для имеющегося функционала. Ни в документации на метод, ни в руководстве по улучшению производительности ничего не говорилось о каких-либо его особенностях в плане скорости. В общем, прочитал и забыл.
Часто попадаются задачи, в которых какие-то данные приходится периодически агрегировать. В моем случае это была простенькая баннерокрутилка. При показе баннера добавлялась строчка в raw-таблицу. По крону вызывался скрипт, который в цикле пробегал по сырым данным и, в частности, увеличивал счетчики показов у баннеров. Внутри цикла был такой код:
$banner->hits++;
$banner->save();

Все работало замечательно, но с ростом нагрузки начались проблемы. Крон-скрипту стало не хватать времени, он начал падать по таймауту. В процессе профилирования и научного тыка менялась логика скрипта, менялось все, кроме выше указанного фрагмента кода. Он был вне подозрений. Как оказалось, зря.
Тут-то я и вспомнил и спец.методе saveCounters. Замена кода на такой решила все проблемы с производительностью скрипта:
//В параметрах передается массив "имя поля"=>"на сколько увеличить"
$banner->saveCounters(array('hits'=>1));

Я был удивлен таким решением проблемы и решил сравнить производительность методов, а также заглянуть в их код.
Тестовое окружение: core2 6420, ram 3gb, Zend Server CE 5.5 со стандартными настройками.
Тестовый код:
$start = microtime(true);
for($i=0;$ihits++;
$banner->save();
или
$banner->saveCounters(array('hits'=>1));
}
echo microtime(true)-$start;

Было проведено несколько измерений, результаты такие:
— save() ~39секунд
— saveCounters() ~23секунды
т.е. saveCounters быстрее на ~41%!
Так в чем же секрет?

Все оказалось очень просто. Код метода saveCounter более простой, но это не главное. Главное, какие sql-запросы формируют эти методы.
Метод save() формирует тяжелый запрос, который включает в себя все поля таблицы.
Примерно такой:
UPDATE `banners` SET `id`=1, `name`='test', `info`='long-long description', `hits`=3560, `iduser`='2', `created_at`='2012-02-17 13:14:02', ... WHERE `banners`.`id`=1

Метод же saveCounters() формирует оптимальный для данной операции запрос:
UPDATE `banners` SET `hits`=`hits`+1 WHERE `banners`.`id`=1

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


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


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