Как стать автором
Обновить
0
DIAFAN.CMS
Российская компания-разработчик ПО DIAFAN.CMS

Как сделать быстрое веб-приложение или 8 советов разработчикам по оптимизации кода

Время на прочтение8 мин
Количество просмотров5.4K
Легкая и быстрая — вот два слова, на которые мы молимся, создавая Diafan.CMS. У нас нет больших библиотек на случай атомной войны, а всё новое добавляется по необходимости. Общая логика системы доработана и отполирована за многие годы, поэтому в системе можно себе позволить ветвить и дорабатывать функционал, оставляя простым для понимания код и легкой для разработки CMS. Как это достигается? Мы сформировали несколько советов.

Наша система неоднократно завоевывала звание быстрой и оптимизированной системы, код которой максимально ярко показывает себя под нагрузками (первое исследование, второе исследование). В комментариях к исследованиям мы обещали, что напишем статью о том, как достигаются такие результаты. А где еще публиковать такую статью, как не на Хабре?

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

1. Масштабируйте Ваше приложение только по потребностям


Двигайтесь от простого к сложному.

Допустим, Вашему проекту понадобились SMS-уведомления. Код отправки SMS достаточно простой, в несколько строк: это просто валидация телефона и GET-запрос к серверу отправки SMS — прямо так его и интегрируйте.

Например, когда в Diafan.CMS потребовались SMS-уведомления администратору на поступление заказов, мы поместили этот простой код прямо в модуле «Магазин», в функции, формирующей заказ.

Затем SMS-уведомления нам понадобились в модуле «Обратная связь» и «Рассылки». Тогда мы вынесли этот функционал в виде глобальной фунцкции в отдельный файл includes/sms.php и спокойно использовали в разных модулях в таком виде:

include_once('includes/sms.php');
Sms::send($message, $to);

Когда потребовалось дополнить SMS-уведомления настройками и своим интерфейсом, мы перенесли этот функционал в отдельный модуль.

И если понадобится возможность подключать разные бэкенды, например, чтобы работать с разными SMS-операторами, это можно делать в модуле, общая структура системы это позволяет.

Суть в том, что не нужно на первых этапах разрабатывать какие-то сложные библиотеки для работы с новым функционалом, не нужно выносить 100500 настроек, которые запутают администратора сайта и потребуют написания кипы документации. А потом может не понадобится. Лучше сделать только то, что требуется на данном этапе.

Это не приводит к снежному кому, если система имеет четкую иерархию «код — подключаемые функции — подключаемые модули». Ваше приложение останется простым и легким для понимания на каждом этапе разработки.

Отказываясь от лишнего кода, который не используется на текущем этапе разработки, можно убить сразу двух зайцев:

  • полный контроль кода, исключающий логические ошибки и проблемы безопасности;
  • общий размер приложения не будет расти.

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

2. Уделяйте внимание обработке URL-ссылок


Разбор ссылок сервером — это узкое место, через которое проходят все страницы сайта. Старайтесь делать это место как можно проще для сервера.

Чаще всего для адресной навигации по страницам сайта, вместо простых для сервера адресов вида site.ru/?PAGE=34, используется так называемое «ЧПУ» (человеко-понятные-урл), когда каждой странице сайта соответствует красивый строковый понятный адрес. Например, по адресу site.ru/o_kompanii/ открывается страница «О компании». Здесь «o_kompanii» — это строковое ЧПУ, интерпретируемое в site.ru/?url=o_kompanii по которому CMS ищет страницу с именем o_kompanii в базе и выводит её содержимое на сайте. Как если бы её запросить по PAGE=34. Однако, помимо строкового имени страницы, через адресную строку могут передаваться еще и некоторые параметры и их может быть много. Анализ таких ЧПУ может быть весьма затруднительным.

В Diafan.CMS всего один шаблон для построения ссылок: /строковоеЧПУ/числовойпараметр3/другойчисловойпараметр25/ 3, 25 — это значения параметров. Т.е. если в URL есть числовые параметры — значит перед ними стоят переменные, во всяком случае это самая частая структура.

Мы сразу отбрасываем все числовые параметры, а затем уже, если что-то осталось, смотрим строковую ЧПУ в базе данных и страницу, ей соответствующую. Если по ЧПУ страницу не находим, то начинаем прибавлять по одному параметру к ЧПУ и снова смотреть в базе. Да, такой алгоритм состоит из нескольких лишних SQL-запросов, но они будут только для таких особых страниц или для страниц 404, если искусственно подставить туда переменные. Что не нагружает базу для всех остальных (а их большинство) страниц.

Проще этого только некрасивые ссылки с $_GET данными, типа site.ru/?url=page:13&m=3&m2=25.

3. Запускайте функционал только по необходимости


При запуске любой страницы сайта сначала загружайте только самый необходимый функционал, который в 99% случаев будет использован. Все остальное запускайте только по потребностям.

Например, если в Diafan.CMS к товару нужно подключить рейтинг, мы просто обращаемся к переменной $this->diafan->_rating и она отдаст всю подключаемую часть модуля «Рейтинг». Так как при первом обращении к псевдопеременной $this->diafan_*** будет проверено наличие запрашиваемого модуля и выполнено корректное подключение к нему, как экземпляру класса. Это удобно и в использовании, и отсекает любой лишний функционал в текущий момент исполнения скрипта. Если рейтинг на странице не нужен, он не будет запускаться вообще.

4. Функционал, создающий дополнительную нагрузку, подключайте опционально


Не подключайте дополнительный функционал глобально во все модули, давайте возможность включить его опционально.
Например, в любом модуле, где используются комментарии, в Diafan.CMS есть опция «Подключить комментарии». Это исключает ситуации, когда комментарии к товарам нужны, а к новостям не нужны, и веб-мастер просто скрывает в шаблоне новостей вывод комментариев, но CMS продолжает выполнять SQL-запрос, искать комментарии к новостям, формировать все необходимые данные. Иными словами тратить ресурсы сервера понапрасну.

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

Это же касается скрытых настроек, например «Права доступа», когда нужно гибко настраивать ограничения доступа к разным конкретным материалам сайта для разных пользователей (Гость, Пользователь, Оптовик и пр.) Чтобы обеспечить эту возможность, проще всего ко всем SQL-запросам всех материалов добавлять:

… LEFT JOIN access AS a ON a.element_id='id_material' AND (e.access='yes' AND a.role='role_current_user'), который проверяет, есть ли у текущего пользователя доступ к запрашиваемому материалу.

А зачем нам эта нагрузка, если все новости видят все пользователи и этих ситуаций гораздо больше? Есть смысл добавлять этот LEFT JOIN к SQL-запросу опционально. В Diafan.CMS включение/выключение этой настроки просходит автоматически, без участия админа. То есть, если для какого-то материала нужно ограничить доступ, достаточно отметить настройку и запросы к БД усложняются. Если нет, то запросы простые.

5. Минимизируйте количество запросов к БД


Используйте prepare-функции.

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

$rows = SELECT * FROM shop; //запрос товаров
foreach ($rows as $row)
{
  echo "Товар".$row['id']; //вывод товара в цикле
  $img = SELECT * FROM img WHERE shopid=$row['id']; //запрос изображений к текущему товару
  foreach ($img)
  {
    echo "Картинка к товару".$img["link"]; //вывод изображения к текущему товару
  }
  }

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

Таким образом получается 1+10=11 отдельных запросов SELECT к SQL-серверу. Однако, оптимальнее сделать запрос товаров, запомнить все ID полученных товаров, и затем сделать всего один SQL-запрос: выбрать все изображения для всех наших товаров сразу. Получится всего 2 запроса SELECT, вместо 11.

$rows = SELECT * FROM shop; //запрос товаров
foreach ($rows as $row)
{
  $goods[] = $row['id']; //запоминаем полученные товары в массив
} $img = SELECT * FROM img WHERE shopid IN implode($goods[]); //запрос изображений для всех товаров
foreach ($img)
{
  $images[] = $img['link']; //запоминаем полученные изображения в массиве
}
foreach ($goods[])
{
    echo "Товар".$goods[]; //вывод товаров в цикле
    echo "Картинка к товару".$images[$goods[id]]; //и изображений к ним
  }


Но здесь проблема в том, что нарушается логика циклов и код становится неудобным. Нам удалось найти изящное решение, красивое для использования и оптимальное для сервера: функция prepare. Код получился такой:

$rows = SELECT * FROM shop; //запрос товаров
foreach($rows as $row)
{
  $this->diafan->_images->prepare($row['id'], 'shop'); //запоминаем товары
}
foreach($rows as $row)
{
  $images[$id] = $this->diafan->_images->get($row['id'], 'shop'); // запрос изображений функцией get
  echo "Товар".$row['id']; //вывод товаров
  echo "Картинка к товару".$images['id']; //и изображений к ним
}

Функция prepare просто запоминает все элементы, для которых нужно получить картинки. А особенность функции get в том, что SQL-запрос будет выполнен при первом вызове и результат тоже запомнится. Все остальные вызовы get из этого цикла будут работать с полученными в первый раз данными.

И так сделано для любого функционала: запрос ЧПУ для формирования ссылок, запрос тегов, запрос рейтинга и пр. Поэтому в Diafan.CMS обращения к SQL-серверу минимальны.

6. Используйте кэширование


Пишите свои алгоритмы кеширования, используйте серверные.

Если интернет-магазин устоялся и новые товары не добавляются, зачем каждый раз обращаться к БД, чтобы сформировать список товаров для каждого покупателя? Достаточно один раз обратиться к SQL-серверу, сохранить результаты запроса в текстовом файле в папке кеша и затем брать их оттуда. Если в список добавляется новый товар, или запрашивается иная страница, например, отсортированный список товаров, можно сбросить файл кеша, и снова обратиться к БД. Но только один раз, для того, чтобы обновить кеш.

Можно кешировать как участки сайта, блоки, списки, карточки и элементы, так и все страницы сайта. Подробнее.

Важно помнить, что статические ресурсы нагужают сервер на порядок меньше, чем динамические, поэтому старайтесь как можно меньше обращаться к SQL и РНР-интерпретатору. По умолчанию в Diafan.CMS используется файловый кэш, но подключить можно и Memcache. Мы кэшируем только данные. Все оформление в кэш не попадает. Это позволяет экономить место под кэш и как следствие быстрее с ним работать.

7. Минимизируйте динамическое получение данных


Лучше один раз подождать администратору сайта, чем ждать будет каждый пользователь.

Например, в Diafan.CMS есть гибкая система скидок, когда у одного товара может быть несколько цен, каждая с установленной скидкой, еще и в разных валютах. Мы не высчитываем новые цены на сайте для каждого посетителя сайта. Мы делаем это при установке скидки в административной части сайта, считаем все различные варианты цены для выбранных товаров и собираем их в отдельную таблицу shop_price с признаком price_id. Исходная цена на товар определяется как id=price_id. Если id<>price_id, то это уже вариация цены либо со скидкой либо в основной валюте сайта. И в этой таблице сразу указывается для кого эта цена (если она установлена для какой-то конкретной группы пользователей).

Да, если товаров много, при добавлении или изменении скидки администратор будет ждать, глядя на крутящееся колесико. Зато посетителям сайта ждать не придется, ведь при выводе на сайте, а также при поиске, сортировке и прочих ёмких операциях система просто из всех доступных цен из одной таблицы по одному price_id выбирает самую маленькую и всё.

8. Используйте нормализацию БД и индексы в таблицах


Разносите сложную структуру информации по разным простым таблицам и используйте индексы по ним. Бывает, выгоднее сделать два простых запроса, чем строить один сложный INNER JOIN.

В Diafan.CMS в основном все смежные таблицы имеют примерно одну структуру: id, element_id, element_type, module_name. Запросы по ним предельно простые.
SELECT * FROM rating WHERE element_id=3 AND module_name='shop' AND element_type='element'
И по всем указанным полям обязательно стоят индексы.

Заключение


Если разобраться, мы не сообщили ничего нового и почти все пункты очевидны. Однако, теорию знают многие, но на практике применяют не всегда. Надеемся, наши советы помогут собраться, перестать лениться и создавать прекрасные, быстрые и оптимизированные приложения.
Теги:
Хабы:
Всего голосов 25: ↑7 и ↓18-11
Комментарии64

Публикации

Информация

Сайт
www.diafan.ru
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия

Истории