WordPress: кэширование средствами nginx

Опубликовано admin в Втр, 16/08/2011 - 11:43

Уменьшение потребления ресурсов во много раз

Много было сказано про кэширование в WordPress… Сегодня я хочу рассказать о действительно эффективном методе, позволяющем сильно снизить нагрузку.

Метод основан на использовании кэша FastCGI web-сервера nginx.

Идея состоит в генерации статических страниц и отдачи их пользователям, не имеющим cookie комментатора. Зарегистрированным пользователям, а также комментаторам всегда отдаётся свежая страница. Так как читателей, ни разу не оставлявших комментарий, как правило, гораздо больше, чем комментаторов, то подобный использование кэша позволяет значительно снизить нагрузку на WordPress/PHP. Знакомые с принципом работы WP Super Cache заметят, что WPSC использует тот же принцип работы.

В чем же преимущество перекладывания работы на Web-сервер (nginx)?

  1. PHP — интерпретируемый язык. Как следствие, аналогичный код на компилируемом языке будет во много раз быстрее.
  2. WordPress во многом bloatware :-) От момента начала загрузки страницы до окончания выполнения стадии init выполняется очень много «лишнего» кода.
  3. В зависимости от нагрузки на сервер от запроса страницы до выполнения PHP-кода может пройти некоторое время: всё зависит от загруженности PHP (если все дочерние процессы PHP заняты обработкой запроса, новому запросу придётся ждать освобождения одного из процессов. Если за приемлемое время ни один процесс не освободился, пользователь видит сообщение о печально знаменитой ошибке 504).
  4. Когда обслуживанием кэша занимается web-сервер, шансы на возникновение подобной ситуации
  5. (когда даже автор не может понять, в чём дело) значительно снижаются: реализация синхронизации/блокировок средствами PHP — неблагодарное дело.

Теперь переходим от теории к практике.

[-]
View Code nginx configuration
  1. fastcgi_cache_path /var/lib/nginx/myblog levels=2 keys_zone=myblog:10m max_size=512m inactive=20m;
  2. server {
  3.     server_name example.com;
  4.     root /path/to/blog;
  5.  
  6.     index index.php;
  7.  
  8.     if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
  9.         set $do_not_cache 1;
  10.     }
  11.  
  12.     fastcgi_cache_bypass $do_not_cache;
  13.     fastcgi_no_cache $do_not_cache;
  14.     fastcgi_pass_header Cookie;
  15.     fastcgi_cache myblog;
  16.     fastcgi_cache_key $request_method|$host|$request_uri;
  17.     fastcgi_cache_valid 301 8h;
  18.     fastcgi_cache_valid 404 1h;
  19.     fastcgi_cache_valid 200 15m;
  20.  
  21.     if ($http_user_agent !~ FeedBurner) {
  22. # Здесь идут перенаправления для FeedBurner
  23.     }
  24.  
  25.     error_page 404 = @wordpress;
  26.     log_not_found off;
  27.  
  28.     location ^~ /files/ {
  29.         rewrite /files/(.+) /wp-includes/ms-files.php?file=$1 last;
  30.     }
  31.  
  32.     location ~ ^/(wp-admin/.*\.php|wp-login\.php|wp-register\.php|(feed|comment/feed)(/.*)?)$ {
  33.         try_files $uri @wordpress;
  34.         set $do_not_cache 1;
  35.         fastcgi_cache_bypass 1;
  36.         fastcgi_no_cache 1;
  37.         fastcgi_pass unix:/dev/shm/php-fcgi.sock;
  38.         fastcgi_index index.php;
  39.         include /etc/nginx/fastcgi_params;
  40.         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  41.     }
  42.  
  43.     location @wordpress {
  44.         include /etc/nginx/fastcgi_params;
  45.         fastcgi_param SCRIPT_NAME /index.php;
  46.         fastcgi_param SCRIPT_FILENAME $document_root/index.php;
  47.         fastcgi_index index.php;
  48.         fastcgi_pass unix:/dev/shm/php-fcgi.sock;
  49.         if ($do_not_cache != "1") {
  50.             add_header Vary Cookie;
  51.         }
  52.     }
  53.  
  54.     location ~ \.php$ {
  55.         include /etc/nginx/fastcgi_params;
  56.         fastcgi_index index.php;
  57.         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  58.         try_files $uri @wordpress;
  59.         fastcgi_pass unix:/dev/shm/php-fcgi.sock;
  60.         if ($do_not_cache != "1") {
  61.             add_header Vary Cookie;
  62.         }
  63.     }
  64.  
  65.     location ^~ /blogs.dir/ {
  66.         internal;
  67.         root /path/to/blog/wp-content/;
  68.     }
  69. }

Документация по использованию FastCGI в nginx приведена здесь _http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html, поэтому я не буду подробно останавливаться на том, что конкретно каждая директива делает.

Основная магия происходит в строка 8—10: здесь определяются условия, при которых отдаётся свежая страница. В данном случае проверяется наличие авторизационных cookie и cookie комментатора. Важно, чтобы в случае отказа от кэширования переменная $do_not_cache устанавливалась в 1.

Строка 16 задаёт ключ, по которому страница доступна в кэше. В ключе присутствует $request_method, чтобы различать запросы GET и HEAD (для HEAD данные не возвращаются, только заголовки).

Строки 17—19 задают время жизни кэша в зависимости от кода ответа. Чем больше время жизни, тем реже будет обновляться информация для незарегистрированных пользователей.

В строке 32 задаётся маска для страниц, которые не должны кэшироваться (например, админка, страницы логина и регистрации, фиды).

Так как содержимое страницы может отличаться в зависимости от cookie пользователя, в строках 49 и 59 для закэшированных страниц принудительно выставляется заголовок Vary: Cache. Если используются другие правила (например, фильтрация по User-Agent), нужно добавлять соответствующее правило.

Строки 21—23: подробнее описано в статье «Перенаправление RSS в WordPress на FeedBurner для nginx» (_http://blog.sjinks.pro/wordpress/691-redirect-rss-feeds-to-feedburner-with-nginx/)

Строки 28—30, 63—66 подробнее описаны в статье «WordPress MultiSite, nginx и X-Accel-Redirect» (_http://blog.sjinks.pro/wordpress/874-wpms-nginx-accel-redirect/)

Вроде бы ничего не забыл. Теперь о том, какую производительность мы получаем:

[-]
View Code Text
$ ab -n 10000 -c 100 http://blog.sjinks.pro/
Server Software:        nginx
Server Hostname:        blog.sjinks.pro
Server Port:            80

Document Path:          /
Document Length:        50294 bytes

Concurrency Level:      100
Time taken for tests:   46.925 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      504940000 bytes
HTML transferred:       502940000 bytes
Requests per second:    213.10 [#/sec] (mean)
Time per request:       469.254 [ms] (mean)
Time per request:       4.693 [ms] (mean, across all concurrent requests)
Transfer rate:          10508.28 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       16   94  76.9     94    3125
Processing:   118  373  81.7    360    1334
Waiting:       17   69  38.5     57     422
Total:        156  467 105.0    445    3535

Percentage of the requests served within a certain time (ms)
  50%    445
  66%    454
  75%    471
  80%    494
  90%    541
  95%    589
  98%    655
  99%    768
 100%   3535 (longest request)

Сервер зафлудили 10,000 запросами в 100 параллельных потоков. Сервер выдал практически полгигабайта за 47 секунд; в среднем сервер обрабатывал 213 запросов в секунду, при этом 99% запросов были обработаны за 768 мс (при том, что на сервере несколько сайтов в Alexa Top 100,000)! Нагрузка на сервер была минимальной (изменений Load Average замечено не было).

UPDATE: есть один небольшой нюанс: CAPTCHA. Если вы их используете, убедитесь, что они работают. Дело в том, что страница со статьёй будет отдаваться непосредственно из кэша nginx, PHP загружаться не будет. Поэтому если CAPTCHA полагается на использование сессии, она может работать неправильно. Здесь всё зависит от используемого плагина.