Ускоряем модуль "Баннеры"
Дата публикации: 31.07.2014
Версия:2.9.5. Шаблонизатор: TPL
Первая мысль с баннерами - кешировать их также, как это делалось с меню и лентами новостей. К сожалению, в этом случае не будет собираться статистика по показам, а рандомные баннеры начнут вести себя несколько странно. Именно статистика была причиной того, что отказались от статического кеширования средствами nginx, хотя оно было самое производительное и позволило бы немало съэкономить на хостинге.
Для начала попробуем найти узкое место.
Даже при беглом просмотре бросается в глаза нелогичная конструкция:
$sel = new selector("objects"); $sel->types("object-type")->name("banners", "banner"); $sel->where("show_start_date")->less(time()); $sel->where("is_active")->equals(1); $sel->where('place')->equals($placeId); $sel->where('view_pages')->equals($currentPageId); $result = $sel->result(); $selWithoutNull = new selector("objects"); $selWithoutNull->types("object-type")->name("banners", "banner"); $selWithoutNull->where("show_start_date")->less(time()); $selWithoutNull->where("is_active")->equals(1); $selWithoutNull->where('place')->equals($placeId); $selWithoutNull->where('view_pages')->equals($currentPageParentsIds); $resultWithoutNull = $selWithoutNull->result(); $selWithNull = new selector("objects"); $selWithNull->types("object-type")->name("banners", "banner"); $selWithNull->where("show_start_date")->less(time()); $selWithNull->where("is_active")->equals(1); $selWithNull->where('place')->equals($placeId); $selWithNull->where('view_pages')->isnull(true); $resultWithNull = $selWithNull->result();
Ежу видно, что три подряд идущих выборки отличаются только одной строкой: проверкой значения поля view_pages
Методом query() объекта selector выводим на экран исходный текст запроса и ахаем - три запроса с шеститабличным соединением! Нечего удивляться, что баннеры тормозили главную страницу на 30%!
Придется переписать запрос вручную.
SELECT o.id as id FROM cms3_object_types t, cms3_objects o LEFT JOIN cms3_object_content oc_282_lj ON oc_282_lj.obj_id=o.id AND oc_282_lj.field_id = '282' LEFT JOIN cms3_object_content oc_275_lj ON oc_275_lj.obj_id=o.id AND oc_275_lj.field_id = '275' LEFT JOIN cms3_object_content oc_286_lj ON oc_286_lj.obj_id=o.id AND oc_286_lj.field_id = '286' LEFT JOIN cms3_object_content oc_285_lj ON oc_285_lj.obj_id=o.id AND oc_285_lj.field_id = '285' WHERE o.type_id IN (80, 81, 82, 103, 83) AND t.id = o.type_id AND ( oc_282_lj.int_val < {$текущее_время} AND oc_275_lj.int_val = '{$текущая страница}' AND oc_286_lj.rel_val = '{$место_размещения_баннера}' AND ( oc_285_lj.tree_val = '1' OR oc_285_lj.tree_val IN({$список родительских страниц}) OR oc_285_lj.tree_val IS NULL ) );
Казалось бы, теперь все будет хорошо. Как обычно, берем целиком имеющийся макрос, переносим его в __custom.php. Все обращения к $this заменяем на объект модуля. Запускаем и... облом(
Макрос ссылается на статическую переменную arrVisibleBanners. Естественно, из подключаемого абстрактного класса к ней нет доступа. Аналогично, методы getPlaceId и renderBanner объявлены как protected и к ним доступа тоже нет.
Напомню, что начиная с 2.8 в ЮМИ в кастомных макросах обращаться к основному классу нужно не через $this, а через объект модуля. Т.о., строки вида:
self::renderBanner($iBannId); $this->getPlaceId($sPlace);
следует заменить на:
$module = cmsController::getInstance()->getModule('banners');// получили объект модуля $module->renderBanner($iBannId); $module->getPlaceId($sPlace);
Увы, со static и protected это не прокатывает.
Решением может быть хитрый финт ушами. Призовем на помощь механизм отражений, встроенный в php, и доберемся до нужных свойств в обход. Я использовал PHP 5.4.4-14+deb7u12, но и с 5.3, скорее всего, будет работать. Объект-отражение содержит в себе информацию о классе, имя которого передано ему к конструктор.
В итоге доступ к защищенным параметрам будет таким:
$module = cmsController::getInstance()->getModule('banners'); // получили модуль $ReflectedClass = new ReflectionClass($module); //получили объект-отражение // Работа с методами через отражение $renderBanner = $ReflectedClass->getMethod('renderBanner'); // получили protected-метод "renderBanner" модуля Баннеры $renderBanner->setAccessible(true); // сделали его доступным //ниже показано как теперь обращаться к полученному методу: $sResult = $renderBanner->invoke($module, параметр_1, параметр_2, ....); // Работа со статическими переменными через отражение $arrVisibleBanners = $ReflectedClass->getStaticPropertyValue('arrVisibleBanners'); // прочитали значение статической переменной arrVisibleBanners из модуля Баннеры и скопировали ее локально для работы. // а вот так записали измененное значение обратно в модуль $ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners);
Теперь сводим все воедино и получаем кастомный макрос:
public function insertCache($sPlace = "", $iMacrosID=0, $bList = false, $current_element_id = false) { $module = cmsController::getInstance()->getModule('banners'); $ReflectedClass = new ReflectionClass($module); $getPlaceId = $ReflectedClass->getMethod('getPlaceId'); $getPlaceId->setAccessible(true); $renderBanner = $ReflectedClass->getMethod('renderBanner'); $renderBanner->setAccessible(true); $arrVisibleBanners = $ReflectedClass->getStaticPropertyValue('arrVisibleBanners'); if($current_element_id) { $currentPageId = $current_element_id; } else { $currentPageId = cmsController::getInstance()->getCurrentElementId(); } $places = $getPlaceId->invoke($module, $sPlace); if (!count($places)) return ""; $placeId = $places[0]; $place = umiObjectsCollection::getInstance()->getObject($placeId); $bShowRandomBanner = (bool) $place->getValue('is_show_rand_banner'); $sResult = ""; $arrBannersList = array(); $currentPageParentsIds = umiHierarchy::getInstance()->getAllParents($currentPageId); $time = time(); $parents = implode(',',$currentPageParentsIds); if(!$parents) $parents = 0; $sql = <<<EOD SELECT o.id as id FROM cms3_object_types t, cms3_objects o LEFT JOIN cms3_object_content oc_282_lj ON oc_282_lj.obj_id=o.id AND oc_282_lj.field_id = '282' LEFT JOIN cms3_object_content oc_275_lj ON oc_275_lj.obj_id=o.id AND oc_275_lj.field_id = '275' LEFT JOIN cms3_object_content oc_286_lj ON oc_286_lj.obj_id=o.id AND oc_286_lj.field_id = '286' LEFT JOIN cms3_object_content oc_285_lj ON oc_285_lj.obj_id=o.id AND oc_285_lj.field_id = '285' WHERE o.type_id IN (80, 81, 82, 103, 83) AND t.id = o.type_id AND (oc_282_lj.int_val < {$time} AND oc_275_lj.int_val = '{$currentPageId}' AND oc_286_lj.rel_val = '{$placeId}' AND ( oc_285_lj.tree_val = '1' OR oc_285_lj.tree_val IN({$parents}) OR oc_285_lj.tree_val IS NULL )); EOD; $o = umiObjectsCollection::getInstance(); $arrSelResults = array(); $res = l_mysql_query($sql); while($row = mysql_fetch_assoc($res)){ $id = $row['id']; $oBanner = $o->getObject($id); $arrSelResults[] = $oBanner; } mysql_free_result($res); // others filters ========================================================= if((defined("DB_DRIVER") && DB_DRIVER != "xml")||!defined("DB_DRIVER")) if ($oStat = cmsController::getInstance()->getModule("stat")) { $arrGetTags = $oStat->getCurrentUserTags(); } foreach ($arrSelResults as $id => $oNextBanner) { if ($oNextBanner instanceof umiObject) { $iNextBanId = $oNextBanner->getId(); //echo $iNextBanId.'
'; if (!in_array($currentPageId, $oNextBanner->getValue('not_view_pages'))) { // max count views filter if ($oNextBanner->getValue('max_views') <= 0 || $oNextBanner->getValue('views_count') <= $oNextBanner->getValue('max_views')) { $bShowActual = true; $weight = 1; // tags filter $arrBannerTags = $oNextBanner->getValue("tags"); if (count($arrBannerTags)) { $iCurrPageId = cmsController::getInstance()->getCurrentElementId(); $oCurrPage = umiHierarchy::getInstance()->getElement($iCurrPageId, true); if(is_object($oCurrPage)) { $arrPageTags = $oCurrPage->getValue("tags"); } else { $arrPageTags = Array(); } $arrCommonTags = array_intersect($arrBannerTags, $arrPageTags); if (!count($arrCommonTags)) { $bShowActual = false; } else { $weight += count($arrCommonTags); } } // do show till filter $oShowTillDate = $oNextBanner->GetValue('show_till_date'); if ($oShowTillDate instanceof umiDate && $oShowTillDate->timestamp) { if ($oShowTillDate->timestamp < $oShowTillDate->getCurrentTimeStamp()) { $bShowActual = false; } } if ($bShowActual) { // time-targeting filter ======================= if ($oNextBanner->getValue('time_targeting_is_active')) { $oRanges = new ranges(); // by month $sByMonth = $oNextBanner->getValue('time_targeting_by_month'); if (strlen($sByMonth)) { $iCurrMonth = (int) date("m"); $arrShowByMonth = $oRanges->get($sByMonth, 1); if (array_search($iCurrMonth, $arrShowByMonth)===false) $bShowActual = false; } // by month days $sByMonthDays = $oNextBanner->getValue('time_targeting_by_month_days'); if (strlen($sByMonthDays) && $bShowActual) { $iCurrMonthDay = (int) date("d"); $arrShowByMonthDays = $oRanges->get($sByMonthDays); if (array_search($iCurrMonthDay, $arrShowByMonthDays)===false) $bShowActual = false; } // by week days $sByWeekDays = $oNextBanner->getValue('time_targeting_by_week_days'); if (strlen($sByWeekDays) && $bShowActual) { $iCurrWeekDay = (int) date("w"); $arrShowByWeekDays = $oRanges->get($sByWeekDays); if (array_search($iCurrWeekDay, $arrShowByWeekDays)===false) $bShowActual = false; } // by hours $sByHours = $oNextBanner->getValue('time_targeting_by_hours'); if (strlen($sByHours) && $bShowActual) { $iCurrHour = (int) date("G"); $arrShowByHours = $oRanges->get($sByHours); if (array_search($iCurrHour, $arrShowByHours)===false) $bShowActual = false; } } // user tags filter if ($bShowActual) { $arrBannerTags = $oNextBanner->getValue("user_tags"); if (is_array($arrBannerTags) && count($arrBannerTags)) { if ($oStat = cmsController::getInstance()->getModule("stat")) { $arrUserTags = array(); $refererTags = array(); $referer = getSession("http_referer"); if ($referer && $referer == getServer('HTTP_REFERER') && !regedit::getInstance()->getVal("//modules/banners/not-use-referer-tags")) { $refererTags = $module->getRefererTags($referer); } if (isset($arrGetTags) && is_array($arrGetTags)) { foreach ($arrGetTags as $sTmp => $arrTagInfo) { if (isset($arrTagInfo)) $arrUserTags[] = $arrTagInfo["tag"]; } } $iAllowTags = 0; for ($nI=0; $nI < count($arrBannerTags); $nI++) { $sNextTag = $arrBannerTags[$nI]; if (in_array($sNextTag, $arrUserTags)) { $iAllowTags++; } if (in_array($sNextTag, $refererTags)) { $iAllowTags +=5; } } if (!$iAllowTags) { $bShowActual = false; } else { $weight += $iAllowTags; } } } } if($bShowActual) { $bTargetingActive = $oNextBanner->getValue("city_targeting_is_active"); if($bTargetingActive) { $sBannerCity = $oNextBanner->getValue("city_targeting_city"); if ($sBannerCity) { if($oGeoIPMod = cmsController::getInstance()->getModule("geoip")) { $info = $oGeoIPMod->lookupIp(getServer('REMOTE_ADDR')); if (isset($info['city'])) { $sCurrentCity = $info['city']; $city = umiObjectsCollection::getInstance()->getObject($sBannerCity)->getName(); $bShowActual = ($sCurrentCity == $city); } else $bShowActual == false; } } } } if ($bShowActual) { $arrBannersList[$iNextBanId] = $weight; } } else { } } else { } } } } //exit; if (count($arrBannersList)) { $iShowBanId = 0; arsort($arrBannersList); $arrBannersList = array_keys($arrBannersList); if (count($arrBannersList) > 1) { foreach ($arrVisibleBanners as $sNextPlace => $arrPlaceBanners) { $arrBannersList = array_diff($arrBannersList, $arrPlaceBanners); } } if ($bShowRandomBanner) { // random banner srand((float) microtime() * 10000000); $iRandBanInd = array_rand($arrBannersList); if(isset($arrBannersList[$iRandBanInd])) $iShowBanId = $arrBannersList[$iRandBanInd]; } else { reset($arrBannersList); $iShowBanId = current($arrBannersList); } if ($bList) { $params = array(); $params['nodes:banners'] = array(); foreach ($arrBannersList as $itmp => $iBannId) { $arrVisibleBanners[$sPlace][] = $iBannId; $ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners); $sResult = $renderBanner->invoke($module, $iShowBanId); } $sResult = def_module::parseTemplate("", $params); } else { $arrVisibleBanners[$sPlace][] = $iShowBanId; $ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners); $sResult = $renderBanner->invoke($module, $iShowBanId); } } $reg = regedit::getInstance(); if (($reg->getVal("//modules/banners/days-before-notification") || $reg->getVal("//modules/banners/clicks-before-notification")) && $reg->getVal("//modules/banners/last-check-date") < (time()-3600*24)) { $module->sendNotification(); } return $sResult; }
Ускорение достигается за счет трехкратного уменьшения количества тяжелых запросов. По идее, можно еще увеличить производительность, кешируя объекты баннеров, но мне пока и так хватило.