За чистый и ясный код!

Статьи на тему программирования под веб, используя PHP, MySQL, Jquery и многое другое

Высокая нагрузка сайта написанного на Kohana

Октябрь29

Вступление

«A very fast framework. Benchmarking a framework is hard and rarely reflects the real world, but Kohana is very efficient and carefully optimized for real world usage.»

Так написано на главной странице этого фреймворка, так ли это? Да так, но с напильничком. Немного о проблеме.
У меня был заказ от студии creative-cafe.ru на один высоконагруженный проект http://www.jv.ru/longlivers, вроде на девелоперской машине работает хорошо, быстро. Показатели загрузки главной страницы с отколюченными системами кеширования под Ubuntu 11.10 показывают 1-2 секунды (после оптимизации 30-50ms), под виндой вообще караул 4-5 секунд, причём одно и тоже железо, просто ОСи разные, но в этой статье не об этом (советую работать под ОСью приближенной к продакшену). Так вот показатели вроде неплохо 1-2 секунды, а на продакшене сервер то мощнее будет, там вообще летает, но до некоторых действия. Когда заказчик нажал F5 и подержал так секунд 10, сервер смог загрузить контент только через 30 сек… Всего было отправлено около 200-300 запросов… Это уже не порядок, а что если 1000 зайдет?

Конечно о тестировании высокой нагрузки сказать ничего не могу, т.к. не сталкивался с такими тестами, если кто-то что-то знает отпишитесь в комментариях. Естественно я знаю о проблеме и что надо делать, и так начнём.

Инструменты для оптимизации

Первым делом надо позаботиться о БД (структуры, запросы), о кешировании, о сжатии стилей и скриптов.

Оптимизация БД

БД в проекте вообще маленькая 15 таблиц, она полностью сделана по нормализации, поставлены ключи в тех местах которые нужны, оптимизированы все запросы, запросы которые «системные» (об этом позже) закешированы. Всего на главной выполняется 22 запроса после кеширования и занимает это вообще мелочь 2.5 ms.
Для оптимизации таблицы могу порекомендовать юзать функию explain, почитать что такое нормализация таблиц БД и ещё рекомендую настроить и смотреть лог медленных запросов, он будет актуальным когда база наберет инфу и тогда можно смотреть какой запрос превышает порог медленных запросов. Но все это не относится к фреймверкам, это просто понимание работы БД.

Чем же нам может помочь фреймворк? Для работы с БД я использовал ORM, как я мог заметить при работе с разными фреймами, все ОРМы считывают структуры таблицы, что Zend Framework, что Kohana. И так зачем нам каждый раз считывать структуру таблиц, если они крайне редко меняются особенно на продакшене? Для этого надо закешировать эту информацию, ниже приведён класс который позволяет это сделать:

<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
 * Перекрытие метода получения данных о структуре таблиц 
 *
 * @package	Core
 * @author Maxim Nagaychenko <maxnag [at] meta.ua>
 * @copyright  Maxim Nagaychenko
 * @filesource
 */
class ORM extends Kohana_ORM
{
	/**
	 * Кеширование структуры таблицы
	 * 
	 * @see Kohana_ORM::list_columns()
	 * @return array 
	 */
	public function list_columns()
	{
	    $cache_lifetime=360000; // 100 часов
	    $cache_key = $this->_table_name ."_structure";
	    $result = Cache::instance()->get($cache_key);
	    
	    if ($result) {
	        $columns_data = $result;
	    }
	 
	    if( !isset($columns_data)) {
	        $columns_data = $this->_db->list_columns($this->_table_name);
	        Cache::instance()->set($cache_key, $columns_data, $cache_lifetime);
	    }

	    return $columns_data;
	}
}

Данный метод я положил в папку application/classes/orm.php в моделях которые используют наследование от ORM я ничего не менял, т.е.

class Model_Contest extends ORM {
....
}

фреймворк ищет сначала «у меня» этот файл и если не найдет, то найдет «у себя» в модуле ORM. И так этим методом я решил проблему лишних запросов. Также, уже в зависимости от проекта, можно данные запросов которых несут статических характер (у меня это разные настройки, мыло аминов, кол-во данных на страницу и тд) также можно закешировать.

Конечно сам ОРМ нагружает систему, т.к. выдает все данные запроса, т.е. select * from, а как сделать если надо только определённые поля? Я не знаю, кто знает подскажите. Особенно это заметно на большой таблице с большим кол-вом полей и забором большого кол-во данных. Тут конечно больше проблем в БД, но всё же ОРМу тяжко. Использовать простые запросы куда менее ресурсоёмко, но увы проигрываем в простоте построения запросов.

Стили и скрипты

Лучше если все стили и скрипты будут сложены в одном файле соот., это сэкономит на кол-ве запросов к серверу, что тоже время. Например я на главной использую 8 файлов-скриптов, 2 файла стилей, отсюда дополнительно 10 запросов на сервер. Как можно с этим бороться? Для этого я использую модуль Minify. У него хорошая документация, легко подключается к системе и выполняет свои функции, кроме «слепки» файлов в один, он еще может удалять комментарии и различные спецсимволы.

Кеширование

Для системы кеширования я выбрал memcache, он позволяет сохранять данные по типу key=>value, хранит их в ОЗУ сервера, соответственно работа с этим типом кеша очень быстрая.

Что я храню в мемкеше? Данные по структуре таблиц, которые требуются для работы с ORM, различные настройки сайта, которые меняются крайне редко, это у меня кол-во записей на странице, для каждого в раздела, контактные данные для отправки писем. Также по всему сайту есть сквозной блок новостей, которые показывает последние две новости проекта, вот такие блоки я и закешировал, при изменении данных новостей, кеш переписывается на новый. На сайте имеются много модальных окон, они построены на основе jQueryUI, а контент (формы, тексты) строятся каждый раз при создании страницы, а как часто люди при просмотре сайта нажимают зайти, зарегистрироваться и тд? Думаю намного реже, чем просто смотрят контекстные страницы, поэтому html этих окон закеширован, что дало тоже большой прирост. Прирост дало то, что контент в эти окна грузятся с БД, а формы строятся с помощью класса Form, что сильно нагружает систему.

Отказаться где можно от использования ООП

Да, это так. Программы разработанные на ООП занимают в памяти куда больше места, чем обычные процедурные, и на больших проектах заметны тормоза. Ниже я привел примеры построения формы на чистом XHTML и с использование класса Form. По своей сути этот класс всего лишь обёртка над тегами формы, можно обойтись и без него, но честно труднее, т.к. валидация, всякие изменения хорошо работают с этим классом. Также есть класс URL которые выдает полный урл, конечно можно без него обойтись, кто мешает в теге ссылки написать относительный урл? Но этот фреймверк, он должен быть универсальным, поэтому исключить этот класс нельзя. В чем же заключается его универсальность? Например сегодня ваш сайт живет по адресу http://example.com, потом вы решили его перенести по пути http://example.com/blog, что же будет без использования класса URL, понадобится все ссылки перебить с / на /blog, а представляете сколько их может быть по всем файлам, также менять роутеры и прочее… беда, а вот с использованием этого класса позволяет всего навсего в файлах bootstrap.php и .haccess изменить урл сайта на нужный вам и все ссылки тут же преобразуются. Тут надо и делать выбор или писать без этого класса, выигрыш в скорости или с ним, но тогда потеря производительности, но универсальность. Думайте сами.

Конкатенация содержимого файлов

При работе сайта по управлением Kohana подключается множество файлов через внутренний автолоадер, только системных файлов порядка 70, остальные сильно зависят от подключённых модулей и достигает до 260 на главной странице сайта. Время которое мой локальный комп подключет эти файлы вообще смешное 5 ms, я бы и не обращал на это внимание, но заказчик попросил объединить это все в один файл и уменьшить кол-во инклюдов… Ну что тут скажешь, он платит.
Нашелся этот модуль у одного блогера, описание и проблема решения тут SuperCache. После его применения кол-во фалов сократилось до 75 c 260.

Инструменты профайлинга

Для отслеживания «узких» мест я использовал различные инструменты. Например мне понравился DebugToolbar, это чисто информационный модуль который показывает все необходимые глобальные переменные со значениями, время выполнения различных частей фреймворка, запросы на странице, какие модули подключены, какие роутеры есть и какой задействован и много другое, это все я указал в примере, внизу страницы, также показывает замеры времени по меткам поставленным с помощью класса Profiler.

Второй инструмент это класс Profiler и он встроен в ядро фреймворка. Как раз с помощью этого класса я смог поставить точки в разных частях сайта и посмотреть какая часть сколько времени отрабатывается. Например интересует сколько времени занимает прорисовка формы, берем ставим точку запуска в начале формы и в конце точку остановки. Профайлер нам выдаст сколько времени это заняло, как раз это и мне помогла найти «узкие» места. Как работать с профайлером опишу в другой статье.

Примеры

Я тут приведу всего один пример, который покажет 2 одинаковых формы (просто прорисовка без логики работы). В самом низу формы как раз расположен профайлер, в нём есть раздел «Построение формы» и в нем два пункта, как можно убедится результаты ощутимы — построение на чистом html в тысячи раз быстрее чем строить тоже самое на ООП, особенно если представить это в рамках большого проекта. Обратите внимание еще на кол-во используемой памяти.
Также в верхнем правом углу расположен DebugToolbar, там тоже есть нужная инфа для работы.

Пример

PS Нужны скидки на товары или услуги? Приобретайте купоны на www.newsavings.ca, которые помогут Вам сэкономить ваши средства.

Статья просмотренна 472731 раз, зашло посетителей 41010

Как построить запрос типа WHERE MATCH AGAINST в Kohana3

Сентябрь2

Конструктор запросов

Нравиться мне Фреймворк Kohana, он легкий простой, пока дает все что мне нужно от моих задач.

И вот случилось у меня некая трудность, как я думал в начале, с написание запроса в условиях которого содержится конструкция MATCH(col1, col2,…) AGAINST(»text search»). На данный момент ORM я не юзаю, т.к. не представляю как можно там получить записи при объединении нескольких таблиц, ил запись с таблицы со сложным условием, но то ли еще будет…

Немного отступлю от проблемы, вкратце расскажу, для непосвященных, что это за конструкция. Это конструкция позволяет делать полнотекстовый поиск в наборе указанных полей. Имеет некие настройки для поиска, ищет в указанных полях. Для работы нужна таблица только! типа MyISAM, а также индекс FULLTEXT на искомые поля. Поля должны быть типа VARCHAR, CHAR, TEXT. Более детально на официальной документации.

И так, нужно составить запрос типа:

SELECT * FROM `table` WHERE MATCH(`col1`) AGAINST("search text");

Можно поступить двумя способами.
1 Просто написать такой запрос и запихнуть его в конструкцию:

$data = DB::query(Database::SELECT, 'SELECT * FROM `table`
WHERE MATCH(`col1`) AGAINST("search text")')->execute()->as_array();

В переменной $data у нас будет результат запроса.

Это самый простой способ получения запроса, но иногда такого мало. Например у меня есть запрос на поиск, который включает себя много полей, разные условия поиска и еще разные условия сортировки. В таком случае уж трудно будет изменять запрос… (У меня в модели несколько методов наразные части запроса — очень удобно управлять запросом).

2 Более сложным, но более эффективным путем при решении сложных запросов лучше пользоваться конструктором запросов. Данный пример вверху можно записать так (пока без части с уловием):

$data = DB::select()->from('table')->execute()->as_array();

Теперь как же быть с условием? Для условий where() существует целый класс Database_Query_Builder_Where в нем содержаться куча методов для построения запросов с логикой AND и OR.
Сам запрос сводиться к 3-м обязательным параметрам:

$db->where('column', 'operator', 'value');
например WHERE id=2 будет записано в виде:
$db->where('id', '=', 2);

Причем если значение это integer кавычек не будет, а если string система сама поставит кавычки.

Вернемся к нашему выражению.

SELECT * FROM `table` WHERE MATCH(`col1`) AGAINST("search text")

Что же здесь у нас столбец, что оператор и что значение??? Поставив такое выражение

$db->where('MATCH(col1)', '', 'AGAINST ("search text")');

я получи вид

WHERE `MATCH(col1)` 'AGAINST(\"search text\")'

естественно запрос не сработал… Немного поразмыслив я попытался обратиться к классу DB::expr(), который позволяет вставить в запрос выражение «как есть», т.е. не обрамляет его символами ` . И вот что получилось:

$query_m = DB::expr(' MATCH(`col1`) ');
$query_a = DB::expr(' AGAINST(addslashes("search text")) ');

$db->where($query_m, '', $query_a);

Естественно при использовании класса DB::expr() приходиться самому заботиться об SQL-инъекциях добавляя ф-цию addslashes, в итоге получилось выражение вида:

WHERE MATCH(`col1`) AGAINST("search text")

И так, подводим итог всей этой ночной писанины:

Для использования в условиях запроса сложных выражений при использовании конструктора запросов, необходимо прибегать к помощи класса DB::expr().
Вот полный пример:

$query_m = DB::expr(' MATCH(col1) ');
$query_a = DB::expr(' AGAINST(addslashes("search text")) ');

DB::select()->from('table')->where($query_m, '', $query_a);

получим запрос вида:

SELECT * FROM `table` WHERE MATCH(col1) AGAINST("search text")

Также можно построить такой запрос:

SELECT * FROM `table` WHERE FIELD_IN_SET('1,2,4,5', `column`);

пишем

$f = DB::expr(' FIND_IN_SET('1,2,4,5', ');
$c = DB::expr(' `column` ');

DB::select()->from('table')->where($f, '', $c);

В итоге

SELECT * FROM `table` WHERE FIELD_IN_SET('1,2,4,5', `column`)

Всем удачи в работе!

Статья просмотренна 84844 раз, зашло посетителей 22212

  

Облако тегов

cli csv dump events form Kohana locale models MySQL mysqldump orm PHP tools trigger validate газ газовый счетчик итоги кеширование переменные

Облако тегов плагина WP Cumulus для WordPress требует для просмотра Flash Player 9 или выше.

Я на твиттере!

  • у твиттера тоже бывают перерывы...

Календарь

Сентябрь 2017
Пн Вт Ср Чт Пт Сб Вс
« Июл    
 123
45678910
11121314151617
18192021222324
252627282930  

Сейчас на сайте