Намба 1: Ресурсы
У любого веб-проекта, и особенно у стартапа, всегда есть резкие наплывы посещамости, вызванные разными причинами — публикация статьи на крупном ресурсе (например, на Хабре), пресс-релиз, рекламная кампания, неожиданное упоминание в новостях, итд. Часто (как в последнем примере) всплеск посещаемости происходит ВНЕЗАПНО™.
Это подталкивает к важному выводу — поместить ресурс с посещаемостью в 100к уников в день на начальном VPS за 200 рублей, конечно, можно. Это будет большой повод для гордости в кругу друзей-гиков, однако, скорее всего, приведет к падению сайта в самый ответственный момент. Вообще, хорошо, когда веб-продакшн система работает в обычном режиме не более чем на 10% от ее мощности. Это позволит ей выстоять в момент всплеска посещаемости. Исходя из всего этого, даже такой «легкий» сайт как Шортики лучше поставить на сервер с приличным запасом мощности.
Намба 2: FrontEnd и Backend
Одна из первых заповедей оптимизации веба гласит — разделяй контент статический и динамический. Сделаем мы это стандартным решением: Nginx на фронтенд, Apache слушает на 127.0.0.1. Nginx отдает статический контент, а если видит, что запрос идет к динамическому — отдает запрос «внутрь» апачу:
server {
listen 10.0.121.124:80;
server_name shortiki.com www.shortiki.com;# Унифицируем домен
if ($host = 'www.shortiki.com' ) {
rewrite ^/(.*)$ http://shortiki.com/$1 permanent;
}access_log /var/log/vhosts/nginx-shortiki.com-access.log main;
# Статику отдает нгинкс, пусть браузеры возвращаются за ней раз в месяц. А логи запросов к картинкам нам не нужны.
location ~ ^.+.(html|jpg|jpeg|gif|png|ico|css|js)$ {
root /usr/home/vhosts/shortiki;
expires 30d;
access_log off;}
location / {
proxy_pass http://127.0.0.1:8081;
# Здесь в нашем случае можно придумать хитрое кеширование, но это тема для отдельной статьи
}
}* This source code was highlighted with Source Code Highlighter.
Намба 3: MPM
Сегодня Apache умеет работать в двух основных вариантах MPM (Multi-Processing Modules, они определяют, каким образом веб-сервер работает с тредами/дочерними процессами, итп) – prefork и worker.
Prefork работает по следующей схеме – много процессов, один тред на процесс, процессы обрабатывают запросы. Prefork отличается стабильностью, однако потребляет больше памяти и работает медленнее чем worker.
Worker отличается тем, что использует много процессов, много тредов на каждый процесс, запросы обрабатывают треды. Работает worker намного быстрее конкурента и использует меньше памяти, но изоляция между процессами у него неполноценная, что может создать проблемы на сайтах, где есть сессии, регистрации, ну и прочие важные данные. Учитывая популярность префорка, мы будем разбирать оптимизацию на его примере, хотя, в моем случае, с Шортиками логичнее использовать worker.
Намба 4: Accept-фильтры
Accept-фильтры (в данном случае) представляют собой модуль ядра, который буферизирует входящие соединения, и передает запрос веб-серверу только тогда, когда полностью получен корректный HTTP запрос.
В моем случае используется FreeBSD, следовательно, подгружаем модуль:
# kldload accf_http
Делаем так, чтобы модуль подгружался при каждой загрузке системы:
echo 'accf_http_load="YES"' >> /boot/loader.conf
Настраиваем серверы, для апача:
AcceptFilter http httpready
И nginx:
listen 10.0.121.124:80 default sndbuf=16k rcvbuf=8k accept_filter=httpready
И перезапускаем веб-серверы:
/usr/local/etc/rc.d/nginx reload
/usr/local/etc/rc.d/apache22 restart
Намба 5: Тюнинг Apache
Тюнинг Apache традиционно начинается с самой главной рекомендации — первым делом необходимо отключить лишние модули. После того, как ненужные модули выброшены, начинаем подкручивать основные настройки:
MaxClients – параметр, указывающий максимально количество одновременных соединений, которые может держать сервер. Если MaxClients = 300, то при 301 одновременном запросе, последний запрос встанет в очередь, и будет ждать, пока один из процессов освободится, чтобы его обслужить. Основной ресурс, ограничивающий MaxClients, это оперативная память – 300 созданных дочерних процесса апача должны поместиться в памяти одновременно. Обычно принято высчитывать MaxClients исходя из количество свободной памяти:
MaxClients = СвободнаяПамять / РазмерПроцессаАпача
Размер дочернего процесса Apache можно посмотреть в колонке RSS вывода команды top или ps.
Отключаем AllowOverride:
<Directory />
AllowOverride none
</Directory>* This source code was highlighted with Source Code Highlighter.
Дело в том, что если оставить его его включенным, это заставит апач каждый раз делать запрос к файловой системе, проверяя есть ли файл .htaccess.
Выключаем ExtendedStatus (добавляет 1 или 2 системных вызова на каждый реквест):
ExtendedStatus Off
Добавляем FollowSymLinks для веб-директории, иначе Apache каждый раз будет делать проверку симлинк перед ним или файл:
<Directory />
Options FollowSymLinks
</Directory>* This source code was highlighted with Source Code Highlighter.
Уменьшаем таймайут:
Timeout 10
Добавляем сжатие некоторых типов данных (экономит много трафика):
<Location />
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml application/x-javascript
</Location>* This source code was highlighted with Source Code Highlighter.
MinSpareServers и MaxSpareServers – эти параметры, указывающие, какое количество дочерних процессов держать «в холостую». Например, если сейчас запросами заняты четыре процесса, а MinSpareServers равен 2, то апач запустит еще 2 процесса, которые будут простаивать в ожидании запросов. Хитрость в том, что создание нового процесса – относительно ресурсоемкая задача, и, в принципе, настройки тут сводятся к тому, чтобы избежать ситуации, когда сервер начинает постоянно создавать/убивать процессы.
MinSpareServers 2
MaxSpareServers 8
При таких настройках, апаче всегда будет держать 2 процесса в ожидании запросов, но в то же время, если простаивает уже 8 или более процессов – начнет их прибивать, освобождая ресурсы.
Перед Хабраэффектом можно поставить и побольше:
MinSpareServers 8
MaxSpareServers 32
StartServers – количество дочерних процессов, которые Apache создаст при запуске. Если StartServers меньше MinSpareServers, то апач догонит значение до MinSpareServers. Эта настройка зависит от изначальной нагрузки на сайт, и количества ресурсов в нашем случае:
StartServers 8
MaxRequestPerChild указывает сколько запросов обработает процесс перед тем, как будет убит и запущен новый вместо него. Это делается для того, чтобы нивелировать влияние утечек памяти. Если ваш код написан хорошо и у вас нет таких проблем – можно смело повышать значение, однако выше 10000 ставить не рекомендуется, раз в 10000 запросов передернуть процесс это не страшно, зато хорошая профилактика.
MaxRequestPerChild 3000
KeepAlive – настройка, позволяющая обработать несколько запросов в пределах одной TCP сессии, и не открывать новую сессию на каждый запрос. Важно оставить значение KeepAliveTimeout низким, т.к. при большом количестве запросов много процессов будут проводить время в ожидании, и тогда апачу нужно будеть запускать еще процессы.
KeepAlive On
KeepAliveTimeout 5
Намба 6: Тюнинг PHP
В нашем конкретном случае, мы сведем тюнинг php к установке php модуля memcache и самого memcached демону, поскольку все остальные параметры особо не повлияют на производительность. В FreeBSD это просто:
cd /usr/ports/databases/pecl-memcache
make install clean
cd /usr/ports/databases/memcached
make install clean
Намба 7: Добавляем мемкеш
Почти все пользовали, которые зайдут на сайт — зайдут с главной страницы. Следовательно, ее важнее всего оптимизировать. На главной странице у нас один SQL запрос, который берет из базы 20 самых свежих шортиков:
SELECT sid, sdate, stext, srating FROM quotes ORDER BY id ASC LIMIT $shortik_first, $shortiks_main
* This source code was highlighted with Source Code Highlighter.
Лезть в базу каждый раз при открытии страницы нет смысла, а посему, легким движением руки запрос превращается в:
// Устанавливаем соединение с Memcached
$mem = new Memcache();
$mem->connect('localhost', 11211);// Обнуляем переменные
$quotesonpage = '';
// Проверяем есть ли в мемкеше нужная запись
if ( !$mem->get['s_main'] ) {
// Если нет - забираем ее из MySQL...
$connect = @mysql_connect ($server, $user, $pass) or die('Could not connect: ' . mysql_error());
@mysql_select_db("ShoDB");$query = "SELECT sid, sdate, stext, srating FROM quotes ORDER BY id ASC LIMIT $shortik_first, $shortiks_main";
@mysql_set_charset('utf8',$connect);
$get_smain = mysql_query ($query) or die('Cannot execure query: ' . mysql_error());
$quotesonpage = array();
while ($shortik = mysql_fetch_assoc($get_smain)) {
$quotesonpage[] = $shortik;
}$quotesonpage = array_reverse($quotesonpage);
// ...и добавляем в memcache, с временем жизни в полчаса (1800 секунд).
$mem->set('quotes', $quotesonpage,MEMCACHE_COMPRESSED,1800);
} else {
$quotesonpage = $mem->get['quotes'];
}* This source code was highlighted with Source Code Highlighter.
Таким образом, при любом количестве пользователей, нагрузка главной страницы на MySQL равна одному запросу в полчаса. Разумеется, свежие шортики будут появляться на сайте с задержкой в полчаса, но это не страшно.
Намба 8: Милости просим 🙂
Добро пожаловать на Шортики! Надеюсь сайт будет так же тепло принят, как и в узком кругу тех, кто приходил в первые дни жизни ресурса.
Напоминаю, что изначальная цель проекта была в том, чтобы подтянуть навыки программирования, так что не стреляйте в пианиста программиста, он пишет как может 🙂