Оптимизация WEB сервера

Намба 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: Милости просим 🙂

Добро пожаловать на Шортики! Надеюсь сайт будет так же тепло принят, как и в узком кругу тех, кто приходил в первые дни жизни ресурса.

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

Добавить комментарий

Войти с помощью: