Эта небольшая статья посвящена описанию моих действий по приведению
Интро
Итак, дорогие мои хай-тек специалисты и случайные читатели, история эта началась в конце апреля 2012 года, когда мой любимый городской провайдер-монополист наотрез отказал мне в дальнейшем размещении моего трех-ядерного атлона в своей стойке.
Я живу в маленьком курортном городке (3 тыс.мы-местные и 50 тыс.понаехали-тут-летом) и большинство моих персональных проектов посвящено родному и любимому городу. Пара городских вебкамер, погода, справочники и т.д. Кроме того, я ещё чуток борюсь за справедливость и права: организовал небольшой проектик на домене TV — снимаем репортажи про беспредел чинушей и т.д...
Так вот о чем это я… ах да! Стоял, значит, мой двух-юнитовый серверочек (его и сервером тяжко-то назвать) совершенно бесплатно в стойке у провайдера, у которого стоек было аж 3 и 2 из них стояли пустые. Почему же бесплатно? Потому что этому самому провайдеру я помогал развивать корпоративные сайты. Кроме того, других людей, с аналогичными моим знаниями в нашем маленьком городке просто нет — далекая периферия, умные быстро уезжают в Москву и другие зарубежья.
В общем, крутились на AthlonX3 / 4Gb RAM мои проекты и я совершенно не заморачивался по вопросу оптимизации, наладки — делал себе сайтики на любимом wordpress и даже не думал, что когда-нибудь халява и кончится.
После очередной публикации любительского репортажа-недокомпромата на местного мэра ко мне подошел директор провайдера (из той же партии что и мэр) и порекомендовал в течении суток забрать сервер. Компроматики-то на нём лежали. Ещё хорошо, что подошел и сказал.
Суток уже не оставалось. А проектики мои уже и в SERP'е хорошо сидели, и позиции терять не хотелось… Суть — за 12 часов был найден первый попавшийся дешевый
Кстати, ограничение в $10 взялось не просто так — именно такая сумма у меня была на тот момент на webmoney, накапало в Директе. Собственно, этот бюджет поднялся всего на $2 — за дополнительный IP. Сейчас мне этот
Лишь бы поисковики видели
Параметры
Я вроде как и не программист — так себе,
быдлоламо-кодер, и не администратор — знаю yum install, yum erase, make && make install… Но зато я более-менее владею незаменимым навыком гугл-обучения.
Изначально я поставил apache фронт-эндом. Это было нечто :) ab2 со старого сервера показывало не более 1.4 requests per second. Nginx я тогда видел в первый раз. Поставил, почитал (чуток, времени не было) кое-как накопипастил конфигов — завелось. И даже стало быстрее. После установки eaccelerator результат ab2 в 6 r.p.s мне показался просто отличным и я это всё дело так и оставил.
Никакого mysqltuner.pl, никакого выбора/оптимизации apache mpm, даже nginx кушал столько памяти, что 80% забитого свопа мне казалось нормальным. Но видимо это было из-за стресса :) На мой взгляд, сайты не тормозили, ну чуток задумывались. Но зная свою аудиторию — я прекрасно понимал, что мои посетители никуда не уйдут.
Что-то как-то уж очень медленно
Сайтики росли и ширились — неспешно, но выйдя в TOP-5 Google и Яндекс по запросам относительно моего города-курорта, сайты летом стали часто недоступны (RAM/SWAP — 100%). Поскольку времени заниматься оптимизацией летом реально не было — я задачу отложил на конец года.
С конца апреля до 7 декабря в таком режиме «еле-едет» сайтики мои наработали вот такой статистики:
.SU — это WordPress Network — сетка сайтов, посвященная моему городу. Остальные — «одиночные» WordPress'ы. P.S. Когда всё разнесено на поддомены — удобнее обслуживать. Кстати, я только сегодня прочитал про вынесения статики на поддомен, буду пробовать, но позже.
(Прошу в комментариях не описывать в подробностях, как плох WordPress, его код и какое у него ресурсопотребление. Сам столкнулся с этим, как головой об стену. Возможно, это суть следующей статьи.)
Кстати, важным будет упомянуть тот факт, что за этот период у меня начали появляться клиенты в городе, которым нужно было сделать сайт. Я особо не заморачивался — штамповал на WP. И в один прекрасный момент, с поднятием очередного сайта, это всё нагромождение WP стало работать из рук вон плохо.
Читаю, ставлю, конфигурю, тюню, катаю, сношу
yum update принес много обновлений. Но они особо не помогли, да и не могли помочь. Я знал, что надо переходить на php-fpm и заводить кеширование. Для моего случая кеширование наиболее актуально, поскольку абсолютное большинство моих сайтов практически статично — expire в пару недель всех устраивает. А те сайты, которые не статичны (а это в первую очередь, тяжелый WordPress Network), можно аккуратненько подкрутить так, что бы кеш обновлялся строго там где надо и в то время в которое надо.
В общем, php-fpm без бубнов ставиться не хотел. Через пяток другой репозиториев нашел скомпиленый пакет, со всеми необходимыми мне опциями. В общем-то можно было из сорцов — но не хотелось. Установил, по быстрому настроил… httpd stop && php-fpm start — ООО! 9 r.p.s! Иду на рекорд! Шутка конечно.
php-frm стоит, а eaccelerator-то пришлось снести — не совместим, нужно новый. Гугл-обучение — оказывается этот акселератор уже давно устарел. yum install php-pecl-apc. Поставилось, опять не без бубнов — поднастроил, завелось. Генерация страниц стала работать как-то очень странно. Первое открывание долгое — потом на какое-то время сайты начинают летать. Потом тупить. Потом опять летать. Долгое гугл-обучение привело меня к описанию использования APC в Facebook. Это оказалось полезным, многое стало понятным.
Настроил php-fpm. Лазаю по сайту — тут вдруг 502, F5, F5, 504. Что за байда! php-fpm перестает откликаться через какое-то время. Опять читаю — памяти мало, поставил больше. Лучше, но ещё через некоторое время опять 502. Читаю, читаю, memleaks, deadlocks, полный комплект.
Сношу APC, ставлю XCache. Вообще не завелось. Ладно. Надо думать и читать. Ставлю обратно APC. Опять настраиваю. С другим конфигом пока вылетать перестало. Да и скорость значительно поднялась — время генерации особенно простых страниц уменьшилось в несколько раз (2,7s -> 0.8s). Да, ребятушки, 2,7s — это у прокачанного WordPress простая страница. А если плагинов понавесить — как у меня на .TV — так там вообще без оптимизации только генерация 19-25s.
И тут вдруг я понял! Сначала нужно настроить весь окружной софт, который обкатан у множества людей и великолепно справляется со своими обязанностями. Я бросил ковырять php-fpm+apc — и начал настраивать с самого «переднего» края — с сетевого стека операционной системы. Рекомендаций в сети много, всё подробно расписано — повторять не буду. Настроил, проверил, nginx стал шевелиться гораздо быстрей, да и mysql чуток ускорился.
Далее nginx — уменьшил кол-во воркеров до 1 и конекшенов до 8196. Включил sendfile и aio, проверил величину directio. Подкрутил output_buffers согласно мануалу. И вот! Я получил на статике чуть больше 60 r.p.s. Я подумал, что первая победа есть и нужно продолжать. Аналогичным образом прошелся по php-fpm (пока не трогая apc). Конекшены, лимиты. Затем mysql. В этом мне помог mysqltuner.pl и конечно оф.мануал. После увеличения кешей, и уменьшения кол-ва конекшенов вся работа
Однако, php-fpm продолжал периодически падать. Много читал ссылки в гугле. APC бажный — но он и такой хорош. Вернемся к нему чуть позже.
Хочется спать, а сайты падучие
В качестве промежуточного решения я решил попробовать поставить на все WordPress плагин кеширования — W3-Total-Cache. На будущее хотелось использовать nginx fastcgi cache с модулем cache purge (попробовал — не понравилось, ниже напишу почему). W3TC было выбрано как “быстро-поставил-перед-сном», да и в моем случае – это было оптимальное решение. Если бэк-энд упадет – фронт-энд будет кормить людей статикой (но не все так гладко).
Готовим W3TC следующим образом — Page cache: Disk Enchanced, Database cache — APC, Object cache — APC. Expire — по вкусу. На кодексе вордпресса нашел полурабочий конфиг для раздачи page cache из W3TC напрямую с фронт-энда (через nginx не запуская php-fpm), доработал его. Рестарт и только тут я начинаю понимать, что значит, когда сайты работают быстро. Статика улетает из-под nginx'а ну очень быстро. На чистой статике я получал в ab2 до 800 (!!!) r.p.s.
Конечно, когда я сел писать этот текст — я уже знаю, что ab2 это плохо, а siege — это хорошо. Но на тот момент я этого не знал. И всё же! Сравните начальный показать 1.4 и промежуточный 800! Но на этом всё не закончилось, а только началось! Но только на следующее утро.
Утро вечера мудрЁнее
Проснулся утром от очередной СМСки Яндекса, что сайты давненько в ауте. php-fpm не отвечал на запросы фронт-энда. В результате долго ковыряния, перезапусков и перенастроек выяснилось, что чистка user cache в apc вызывает какой-то мертвый лок в результате чего, php-fpm перестает обрабатывать запросы. И вот тут я подошел к тому, что на
php-fpm ПОЧТИ перестал лочиться, но это почти. Меня не устраивало — я же не могу постоянно сидеть и проверять, работает он или нет. Да и как так — вроде продакшн решение (малюсенькое, но все же), а бэк-энд падает. Плохо. Скачал сорцы последнего APC, компилил с разными опциями. Наибольшую производительность показали Spinlocks, но они и самые падучие (экспериментальные всё ж таки). В общем, на дэдлоки при чистке user cache (db, object в контексте wp+w3tс) это никак не повлияло, как висло так и виснет. В итоге, user cache лежит на memcached, и ведёт себя шустро.
Что я только не пробовал. Остановился по итогу на APC с mmap memory и pthread mutex Locks locking. Написал небольшой скриптик для крона, который раз минуту проверяет живость php-fpm. И поставил fastcgi timeout в nginx в 2 минуты. Пока вдруг тупняк – php-fpm упал – пользователь чуточек пождет (с учетом моей аудитории). Другого решения пока не нашел — если кто сталкивался или знает — предлагайте.
Отдельная песня — это nginx fastcgi cache. После его инициализации nginx cache manager скушал 60Мб памяти из общих 256, показал производительность ниже, чем просто nginx + w3tc (pagecache disk enhanced) и был благополучно отключен.
Итоги оптимизационных процедур
На сейчас я остановился на следующем софте: nginx 1.3.8, php 5.3.19 (+php-fpm), apc 3.1.19, memcached 1.4.15, mysql 5.0.95 обслуживают несколько одиночных WordPress и одну WordPress Network c активированным плагином W3 Total Cache
PHP-сессии находятся в memcached, user cache (db, object) -> memcached, apc.XXXXXX в /dev/shm/, apc работает исключительно как оптимизатор и кешер опкода php.
ab2 на полностью кэшированных сайтах выдает около 800 r.p.s. на блочно кешированных около 200 r.p.s. (округлено в меньшую сторону).
Обновление от сегодня, 12.12.2012
Меня реально утомил постоянно падающий PHP-FPM. Но сегодня, всю ночь у меня был потрясающий сексразбор ключей конфигурации перед компиляцией PHP 5.4.9. Пока я разобрался как собирать его, и его модули из исходников — думал, крыша съедет. Еле собрал, запустил. Сайты работают. User cache в APC не вызывает дэдлок. На момент апдейта публикации — PHP-FPM Uptime 9 hours and 15 minutes без единного нарекания. Однако, я так и не смог понять, как можно скомпилировать PHP в минимальной рабочей сборке под WordPress. Сейчас все модули вкомпилены в сам PHP и я не знаю, как правильно их выключить. Уважаемое сообщество, подскажите пожалуйста, как это можно сделать? Какие реально модули нужны для оптимальной работы WordPress с максимальной минимизацией бинарников PHP?
[PHP Modules]
apc
bcmath
calendar
Core
ctype
curl
date
dom
ereg
exif
fileinfo
filter
ftp
gd
gettext
hash
iconv
intl
json
libxml
mbstring
mcrypt
memcache
mhash
mysql
mysqli
mysqlnd
openssl
pcre
posix
Reflection
session
SimpleXML
sockets
SPL
standard
sysvmsg
sysvsem
sysvshm
tokenizer
xml
zip
zlib
Аттач:
#!/bin/sh
POOL=`curl --retry 3 --retry-delay 1 --connect-timeout 2 --max-time 2 --silent http://127.0.0.1/fpmstatus | grep pool | awk '{ print $2 }'`
if [ "$POOL" != "www" ]
then
echo -------------------------------------------------------------------------- >>/var/log/php-fpm/restarts.log
echo `date` PHP-FPM $host:$port is DOWN >>/var/log/php-fpm/restarts.log
echo `date` first try sigterm PHP-FPM... >>/var/log/php-fpm/restarts.log
killall -9 php-fpm >>/dev/null
sleep 5
echo `date` second try sigterm PHP-FPM... >>/var/log/php-fpm/restarts.log
killall -9 php-fpm >>/dev/null
sleep 5
echo `date` restarting PHP-FPM NOW >>/var/log/php-fpm/restarts.log
/etc/init.d/php-fpm restart >>/dev/null
echo -------------------------------------------------------------------------- >>/var/log/php-fpm/restarts.log
else
sleep 1
fi
exit
Два килла используется потому что бывает так повиснет, что только со второго раза сносится.
Внимание! php-fpm и nginx должен быть настроен для корректной работы location /fpmstatus
extension = apc.so
apc.enabled=1
apc.shm_size=128M
apc.num_files_hint=0
apc.user_entries_hint=0
apc.ttl=0
apc.use_request_time=1
apc.user_ttl=0
apc.gc_ttl=3600
apc.cache_by_default=1
apc.filters
apc.mmap_file_mask=/dev/shm/apc.XXXXXX
apc.file_update_protection=10
apc.enable_cli=0
apc.max_file_size=1M
apc.stat=1
apc.stat_ctime=0
apc.canonicalize=1
apc.write_lock=0
apc.rfc1867=0
apc.rfc1867_prefix =upload_
apc.rfc1867_name=APC_UPLOAD_PROGRESS
apc.rfc1867_freq=0
apc.rfc1867_ttl=3600
apc.include_once_override=0
apc.lazy_classes=0
apc.lazy_functions=0
apc.coredump_unmap=0
apc.file_md5=0
apc.localcache=1
apc.localcache.size=1024
worker_processes 1;
timer_resolution 100ms;
worker_rlimit_nofile 4096;
worker_priority -5;
events {
worker_connections 8196;
use epoll;
}
sendfile on;
aio on;
directio 5m;
output_buffers 8 128k;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
reset_timedout_connection on;
open_file_cache max=8192 inactive=60s;
open_file_cache_valid 60m;
open_file_cache_min_uses 2;
open_file_cache_errors on;
map $host $sid {
wp-net.su -999;
forum.wp-net.su 2;
about.wp-net.su 4;
info.wp-net.su 5;
test.wp-net.su 6;
}
server {
listen IP.IP.IP.IP:80;
server_name wp-net.su *.wp-net.su;
access_log /var/log/nginx/nginx_($host)_access.log;
error_log /var/log/nginx/nginx_(ALL.wp-net.su)_error.log error;
root /var/www/vhosts/wp-net.su;
index index.php;
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { allow all; log_not_found off; access_log off; }
location ~ /. { deny all; }
location ~* /(?:uploads|files)/.*.php$ { deny all; }
if ($request_uri ~* "/files/(.*)"){ set $rtfile $1; }
location ^~ /files {
try_files /wp-content/blogs.dir/$sid/$uri /wp-includes/ms-files.php?file=$rtfile;
expires 7d;
}
location ^~ /blogs.dir {
internal;
alias /var/www/vhosts/wp-net.su/wp-content/blogs.dir;
access_log off;
log_not_found off;
expires 7d;
}
location ~* ^.+.(js|css)$ { expires 2d; }
location ~* ^.+.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
expires 7d;
}
set $cache_uri $request_uri;
if ($request_method = POST) { set $cache_uri 'null cache'; }
if ($query_string != "") { set $cache_uri 'null cache'; }
if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") { set $cache_uri 'null cache'; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $cache_uri 'null cache'; }
location / {
try_files /wp-content/w3tc-$host/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?$args;
}
location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
include /etc/nginx/fastcgi.conf;
fastcgi_connect_timeout 1800s;
fastcgi_read_timeout 1800s;
fastcgi_send_timeout 1800s;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_pass phpfpm;
}
}
[mysqld_safe]
key_buffer=8M
max_allowed_packet=1M
max_heap_table_size=24M
table_cache=1024
sort_buffer_size=512K
read_buffer_size=512K
read_rnd_buffer_size=2M
net_buffer_length=20K
thread_stack=640K
thread_cache=4
tmp_table_size=8M
query_cache_limit=8M
query_cache_size=16M
skip-locking
skip-networking
skip-bdb
skip-innodb
skip-thread-priority
old-passwords=0
thread_concurrency=1
max_connections=16
Господа специалисты, я понимаю, критиковать часто приятно, однако я не специалист, и делюсь даже не столько своими наработками с новичками, а показываю, как можно нащупать путь, удобный каждому. Хотя конечно — критикуйте! И особенно полезны будут дельные рекомендации сообщества — хочу попробовать, потестировать на этом
Хорошего вам настроения
Автор: olo