Тормозит веб-сервер. Настройка на примере www.ochevidets.ru

Разработка веб-сайтов
Я оказываю услуги по настройке web- и БД-серверов. На днях ко мне обратился Иван Усачёв — владелец портала ochevidets.ru с просьбой избавить сайт от торможения.

Страницы в пиковое время стали долго загружаться, вплоть до 5 минут на страницу.

UPDATE: Статья была написана в 2010 году. Кое-что изменилось: вышли новые версии программ, у nginx изменились некоторые директивы и появились новые. Учитывайте это.


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

Для этой цели есть удобные сервисы:
http://site-perf.com/ (точку доступа лучше европейскую выбирать)
http://tools.pingdom.com/

Проблема оказалась не в провайдерах. Рассуждаем дальше.

Раздаётся достаточно тяжёлый видеоконтент. PHP-скрипты довольно простые, не потребляют много процессорного времени. Это я узнал просматривая вывод linux-команды top.

На сервере стоит рейд «зеркало» со скоростью чтения каждого диска около 60 мб/сек (серверу уже несколько лет). К интернету сервер подсоединён каналом 100 мбит/сек, что примерно эквивалентно 10 мегабайтам в секунду учитывая, что примерно 20% данных служебные.

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

А вдруг software рейд перешёл в degraded-состояние? В таком случае его производительность может упасть во многие разы. Смотрю вывод cat /proc/mdstat — все диски [UU], всё ОК.

Рассуждаю дальше. Проблема возникла впервые. Посещаемость увеличилась. Много запросов к дисковой подсистеме. Поскольку много пользователей работают с сайтом параллельно, то винчестер постоянно перепозиционирует свои головки и скорость чтения может феноменально упасть из-за такой неприятной вещи как seek-time.

В добавок как я узнал из вывода команды free из 8 гб свапа использовалось 300 мб, что само по себе очень плохой знак. Запись и подгрузка страниц из свопа может конкретно убивать производительность.

Из вывода той же команды top я узнал, что 70-80% времени процессы находятся в состоянии io — что означает ожидание ввода вывода. В принципе под ввод вывод попадает и сетевая карта, но по отчётам munin пик трафика составлял 70 Мбит, т.е. пиковая производительность не была достигнута.

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

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

Вообще есть замечательная утилита iotop, но для работы с ней в ядре должны быть включены: CONFIG_TASK_IO_ACCOUNTING, CONFIG_TASK_DELAY_ACCT, CONFIG_TASKSTATS.

Ядро было старое, опций этих в нём не было. Поэтому днём я не смог использовать этот диагностический инструмент, т.к. сервак нежелательно было останавливать.

Я предположил, что если загнать самые часто используемые файлы в кеш, то количество перепозиционирований уменьшится в разы и это спасёт сервер.

Самый красивый способ, который я знаю, закешировать огромное количество данных это рейд карта с поддержкой технологии Max IQ. К аппаратному рейд-контроллеру подключается SSD Drive на 64 и более ГБ. И вуа-ля! Мы имеем кеш 64 Гб! Красиво — но дорого для начала нужен сам контроллер с поддержкой Max IQ ($500) и специальный SSD Drive (от $1000).

Место для контроллера в сервере нашлось, а вот для дополнительного винчестера — нет. Сервер имеет очень компактный 1U корпус. Так что я решил для начала использовать системный кеш файловой системы. Чтобы увеличить этот кеш достаточно просто увеличить системную память.

В сервере оставалось ещё 4 разъёма и я нашёл нужную память ориентируясь на маркировку на уже стоявших модулях памяти.

Было установлено 2Gb памяти, я предложил добавить ещё 8 гб. Заказал в интернет-магазине и пока ожидал доставки решил настроить остальные моменты.

Сервер как фронтэнд использует nginx и я решил посмотреть, что можно сделать с ним.

По первых я установил

worker_processes 6;

а было 2. Вообще сервер 8 ядерный, но решил не загружать все ядра процессами nginx и оставить парочку на apache и mysql.

Далее я выставил/проверил чтобы были следующие настройки:

http {

....

# Включить sendfile(). Использование sendfile() экономит системные вызовы, уменьшает число копирований данных,                
# позволяет использовать меньше физической памяти.                                                                            
sendfile        on;

# Сетевое ускорение                                                                                                       
tcp_nopush      on;                                                                                                       
tcp_nodelay     on;

# При большом количестве отдачи мелких файлов и медленном винчестере может помочь временное отключение логов. Это уменьшит число перепозиционирований головок
access_log off;

# сжимаем тестовые файлы
gzip             on;
# мелочёвку не трогаем
gzip_min_length  1000;
gzip_buffers     16 8k;
# что именно сжимать
gzip_types       text/plain text/css text/xml application/x-javascript application/xml application/xhtml+xml;

}

Тяжёлый контент раздаётся в flv и mp4. Вообще раздача видео (говоря по забугорному стриминг/streaming) нуждается в особых настройках. Как я понял, основная фишка стриминга — это возможность со стороны сервера отдавать поток так, чтобы не рвались кадры, если запрос идёт не к самому началу файла.

Для оптимальной раздачи flv-файлов nginx должен быть скомпилирован с опцией --with-http_flv_module.

Вообще также есть специальный модуль для стриминга x264 (mp4), но он не входит в официальную сборку nginx и я не стал с ним экспериментировать.

Далее нужно включить для определённых типов файлов стриминг flv. Примерно это делается так:

http {
    ...
    server {
        ...
        location ~ \.flv$ {
            flv;
        }
        ...
    }
    ...
}

Но в моём случае это не прошло. Дело в том, что на сервере Ивана nginx выполняет роль легковесного фронтенда, а все файлы и запросы всё равно идут через apache. Выглядит это примерно так:

server {
        listen       80;
        server_name  www.ochevidets.ru ochevidets.ru;
...
        location / {
            limit_conn   one  30;
            proxy_pass http://127.0.0.1:8128/;
...
        }
...
}

Если добавить ещё одну секцию location для стриминга flv

location ~ \.flv$ {
            limit_conn   one  2;
            proxy_pass http://127.0.0.1:8128;
            proxy_buffer_size 2m;
...
        }


Но ещё лучше заставить nginx отдавать статику напрямую. Например так:

location /video/ {
  root /home/ochev/html/ochevidets.ru/;
}

location ~ /video/.*\.flv$ {
  root /home/ochev/html/ochevidets.ru/;
  flv;
  # один пользователь может смотреть одновременно 2 ролика
  limit_conn   one  2;
}

Далее я вставил в блок server очень важные настройки для раздачи тяжёлого контента

# Первый мегабайт скачивается без ограничений по скорости,
# потом отдаётся весь поток данных к одному клиенту со скоростью не более 150 кб в секунду
limit_rate_after  1m;
set $limit_rate  150k;

Иначе у какого-то клиента всё будет летать, а у какого-то тормозить. Я решил всё по-коммунистически поделить поровну )

Далее я изучил php.ini и не нашёл какого-либо модуля отвественного за кеширование скомпилированных php-файлов.

Я поставил eaccelerator со следующими настройками:

zend_extension="/путь/eaccelerator.so"
eaccelerator.shm_size="8"  ; Размер shared memory под кеширование скомпилированных скриптов
eaccelerator.cache_dir="/home/ochev/tmp/eaccelerator" ; Путь к директории где будут кешироваться скрипты, когда закончится shared memory
eaccelerator.enable="1"  ; модуль включён
eaccelerator.optimizer="1"  ;  производим оптимизацию при компиляции
eaccelerator.check_mtime="1"  ;  проверяем время модификации файла, чтобы решить берём закешированную версию или производим компиляцию
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0" ; Когда закончится shared memory используем диск
eaccelerator.compress="0"  ; Сжимаем скомпилированные скрипты для экономии shared memory

До и после я протестировал главную страницу сайта с помощью утилиты ab, входящей в состав апача.

ab -c 10 -n 20 -t 20 http://www.ochevidets.ru/

Параллельное число конкурентных потоков — 10, число запросов 20, максимальное время ожидания ответа — 20 секунд

Time per request снизилось на 0.35 секунды на запрос. Эту оптимизацию в полной мере удалось оценить после установки дополнительной памяти: число обработанных запросов в единицу времени с eaccelerator по сравнению с чистым mod_php выросло в 4,11 раза c 22,53 до 92,67 запросов в секунду.

Потом я просмотрел лог медленных запросов сервера mysql. Он включается опцией --log-slow-queries=имя_файла. Нашёл запрос, который исполнялся иногда до 30 секунд. Конец его был примерно такой:

... ORDER BY RAND(), ...... LIMIT ...

избавился от неиндексироваемой сортировки по RAND() разбив запрос на 2 части по технологии. И запрос стал выполняться доли секунды.

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

Заголовки с linux-машины можно посмотреть так:

curl --head полное_имя_файла_с_http

Или через какой-нибудь онлайн-сервис, например, be1.ru/stat

При этом нужно обратить внимание на заголовки:
Cache-control — директивы кеширования для браузера
Expires — когда нужно запросить этот файл напрямую, а не брать из кеша
Pragma — устаревший заголовок, примерно с такими же функциями как cache-control
Etag — что-типа хеша файла. Если он изменился, то браузер запрашивает файл снова

Сделав запрос к одной из картинок через я ужаснулся:

Запрашиваемая картинка: ochevidets.ru/userfiles/1209/images/2010/12/ekaterinburg/0.jpg

Status: HTTP/1.1 301 Moved Permanently
Server: nginx/0.8.53
Date: Wed, 01 Dec 2010 19:47:26 GMT
Content-type: text/html; charset=iso-8859-1
Connection: keep-alive
Location: http://www.ochevidets.ru/userfiles/1209/images/2010/12/ekaterinburg/0.jpg
Cache-control: max-age=3600
Expires: Wed, 01 Dec 2010 20:47:26 GMT
Content-length: 404

Этот ответ заставляет браузер повторно обращаться по другому УРЛ, чтобы получить картинку.
Как видно это происходит по тому, что есть некие правила, которые переписывают все УРЛ так, чтобы они начинались с www.

Дело в том, что в коде сайта многие картинки были прописаны с абсолютными путями, начинающимися с ochevidets.ru даже, если смотришь сайт www.ochevidets.ru. Я уведомил об этом программиста сайта.

Я нашёл неправильные строчки в .htaccess и переписал:

# Было
#RewriteCond %{HTTP_HOST} ^ochevidets\.ru$ [NC]
#RewriteCond %{REQUEST_URI} !^/robots\.txt$    
#RewriteRule ^(.*)$ http://www.ochevidets.ru/$1 [R=301,L]

#Стало
# Переадресуем ТОЛЬКО контентные страницы ochevidets.ru заканчивающиеся на слэш или главную
RewriteCond %{HTTP_HOST} ^ochevidets\.ru$ [NC]
RewriteRule ^((.*/)|)$ http://www.ochevidets.ru/$1 [R=301,L]

Вообще для нагруженных сайтов лучше избегать использования .htaccess, а вместо этого все настройки прописывать в httpd.conf, но это я оставил на другой раз.

После изменений в .htaccess возвращаемые заголовки у картинок улучшились:

Status: HTTP/1.1 200 OK
Server: nginx/0.8.53
Date: Wed, 01 Dec 2010 19:53:24 GMT
Content-type: image/jpeg
Connection: keep-alive
Last-modified: Wed, 01 Dec 2010 09:25:20 GMT
Etag: "62957-1511b-49655e24e2000"
Accept-ranges: bytes
Content-length: 86299
Cache-control: max-age=864000
Expires: Sat, 11 Dec 2010 19:53:24 GMT

Исчезла необходимость для браузера в повторном запросе.

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

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


Что и было сделано.

Через день подвезли память и после её установки сервер по настоящему вздохнул свободно — страницы стали загружаться за 2-3 секунды (генерироваться за доли секунды).

Подводя итоги, конечно, главный фактор убыстрения работы сервера — это дополнительная RAM, но и тонкие настройки nginx и apache, несомненно улучшили работу сервера.

UPDATE из комментов:
  1. Очень ценные советы по настройке БД mysql дал ниже в комментах dulepov (thread_cache_size = 36, wait_timeout = 300).
  2. Своим интересным опытом раздачи тяжёлого контента через nginx поделился alexxxst
  3. В качестве альтернативы утилите ab WoZ использует программу siege
  4. alfa порекомендовал смотреть заголовки с помощью curl -I
Теги:apache.htaccessnginxserverсервервеб-серверweb-serverнастройкатюнингадминистрирование
Хабы: Разработка веб-сайтов
+108
25,2k 328
Комментарии 407

Похожие публикации

Системный администратор
до 120 000 ₽ЭкспобанкНовосибирск
Веб-разработчик (Full-stack)
от 180 000 до 230 000 ₽КАУСМоскваМожно удаленно
Веб-разработчик
от 30 000 до 60 000 ₽CharltonsМожно удаленно
Веб-дизайнер
от 30 000 до 60 000 ₽CharltonsМожно удаленно

Лучшие публикации за сутки