Оптимизация сервера с помощью Nginx и pm-static

Данная статья является частью серии материалов по созданию блога с галереей изображений, предназначенного для оценки производительности и оптимизации. Репозитарий проекта здесь.

Продолжим оптимизацию разработанного приложения. Мы начали с создания эскизов изображений «налету», которое занимает 28 секунд.

Следует признать, что эти 28 секунд требуются только при первоначальной загрузке. После тонкой настройки мы смогли достичь более высокой скорости.

Содержание

Устранение проблем

Предполагается, что вы уже выполнили процесс развертывания компилятора, и приложение уже запущен.

При развертывании Homestead Improved блок на Windows может появиться проблема общих папок. Ее можно решить добавлением параметра type: «nfs»  к папке folder в Homestead.yaml.

Также нужно запустить vagrant up из интерфейса shell/powershell с правами администратора.

До этого требовалось от 20 до 30 секунд на загрузку при каждом запросе. И нам не удавалось увеличить скорость выше одного запроса в секунду.

Процесс тестирования

Мы установили Locust на наш хост и создали простой  скрипт locustfile.py:

from locust import HttpLocust, TaskSet, task

class UserBehavior(TaskSet):
    @task(1)
    def index(self):
        self.client.get("/")

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 300
    max_wait = 1000

Затем загрузли ngrok на нашу гостевую машину и туннелировали все HTTP-соединения через нее, чтобы можно было протестировать приложение с использованием статического URL-адреса.

После этого мы запустили Locust и подключили к приложению 100 пользователей:

На стороне сервера мы использовали PHP 7.1.10, Nginx 1.13.3 и MySQL 5.7.19, на Ubuntu 16.04.

PHP-FPM и настройка диспетчера процессов

php-fpm создает свои собственные процессы. Управление ими настраивается в /etc/php/7.1/fpm/pool.d/www.conf. В данном файле находятся настройки менеджера процессов. Этот параметр поддерживает следующие значения: dynamic, ondemand и static. Лучше использовать значение dynamic, так как оно позволяет серверу определить количество дочерних процессов на основе заданных лимитов в конфигурационном файле.

pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served.
pm.max_children = 6
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 3
; The desired minimum number of idle server processes
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 2
; The desired maximum number of idle server proceses
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 4

Создание процессов происходит по запросу. Но их количество ограничено этими минимальными и максимальными значениями.

После исправления проблемы с общими папками в Windows и тестирования с помощью Locust мы смогли получить скорость примерно пять запросов за 5 секунду. При этом уровень ошибок составлял 17-19% ошибок при 100 параллельных пользовательских сеансах. Но при увеличении количества запросов, на обработку каждого из них серверу уже требовалось более 10 секунд.

Затем мы поменяли установки pm setting на ondemand. Но это еще больше увеличило время начального ожидания и количество сбоев. В результате приложение могло обрабатывать от четырех до  шести запросов в секунду. Время ожидания и частота отказов были аналогичны настройке dynamic.

Затем мы попробовали использовать параметр pm = static, позволяющий PHP процессу использовать максимум ресурсов сервера. Данный параметр означает, что мы постоянно выжимаем максимум из используемой системы.

В результате скорость работы приложения увеличилась на 20%. Хотя количество неудачных запросов все еще было значительным, а время отклика неудовлетворительным. Система была еще не готова к эксплуатации.

Но в Pingdom Tools мы получили приемлемые 3,38 секунды, если система не находилась под нагрузкой:

Это означало, что pm static улучшило производительность. Но только при умеренной загрузке.

Nginx может служить системой кэширования для статического и динамического контента. Поэтому мы попробовали овладеть магией Nginx и попытались поднять приложение на новый уровень производительности.

Кэширование Nginx и fastcgi

proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
fastcgi_cache_path /home/vagrant/Code/ngd-cache levels=1:2 keys_zone=ngd_cache:10m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
add_header NGINX_FASTCGI_CACHE $upstream_cache_status;

server {
    listen 80;
    listen 443 ssl http2;
    server_name nginx-performance.app;
    root "/home/vagrant/Code/project-nginx/public";

    index index.html index.htm index.php;

    charset utf-8;

    proxy_cache ng_cache;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/nginx-performance.app-error.log error;

    sendfile off;

    client_max_body_size 100m;

    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;


      fastcgi_cache ngd_cache;
      fastcgi_cache_valid  60m;
    }

    location ~ /.ht {
        deny all;
    }

    ssl_certificate     /etc/nginx/ssl/nginx-performance.app.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx-performance.app.key;
}

Мы открыли файл виртуального хоста Nginx и добавили приведенные выше настройки. Давайте поясним их.

proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m;

Как объяснялось в статье производительность Apache и Nginx: методы оптимизации, proxy_cache_path используется для кеширования статических ресурсов. Таких как изображения, таблицы стилей, файлы JavaScript. Сам путь должен существовать, поэтому необходимо создать эти директории.

levels означает глубину директорий внутри этого пути/папки.

Keys zone –  это имя. Каждый виртуальный хост должен использовать отдельное имя. max_size означает максимальный размер кэша, а inactive — что элементы времени будут храниться в кэше, даже если они не запрашиваются.

После этого времени простоя кэш для ресурса будет снова заполнен.

fastcgi_cache_purge

Это определяет запросы, которые смогут очистить кэш. Nginx (его модуль ngx_http_fastcgi_module) предоставляет полный комплект инструментов для кэширования. Пример использования указанной выше директивы:

fastcgi_cache_path /data/nginx/cache keys_zone=cache_zone:10m;
map $request_method $purge_method {
    PURGE   1;
    default 0;
}

server {
    ...
    location / {
        fastcgi_pass        backend;
        fastcgi_cache       cache_zone;
        fastcgi_cache_key   $uri;
        fastcgi_cache_purge $purge_method;
    }
}

В этом случае запрос PURGE REST сможет удалить данные из кэша.

Мы добавили заголовки Nginx к ответам, чтобы можно было узнать, обслуживался ли ресурс из кэша или нет.

add_header NGINX_FASTCGI_CACHE $upstream_cache_status;

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

Метод fastcgi_cache_methods полезен для кеширования конкретных методов запроса, таких как POST. GET и HEAD.

Активировав приведенную выше конфигурацию, как для статического, так и для динамического контента, мы запустили Locust. Мы подключили к системе 100 параллельных пользователей. Разница в результатах была поразительной. Того напряжения, в котором раньше находился сервер, уже не ощущалось.

Кэширование Nginx принесло несколько значительных улучшений.

При этом процент неудачных запросов снизился с 17% до 0,53%. Затем мы перешли на страницу теста Pingdom и проверили наш сайт:

Мы видим, что нам удалось сократить время загрузки страницы. Теперь оно составляет меньше секунды!

Мы также протестировали одну страницу галереи, у которой есть дополнительный «багаж» похожих и новых галерей.

Отчет в файле HAR по этому тесту.

Заключение

В данной статье были проверены некоторые моменты, упомянутые в предыдущем материале по производительности Nginx. Также мы проанализировали управление процессом и степень его влияния на время загрузки страницы.

Данная публикация представляет собой перевод статьи «Server-side Optimization with Nginx and pm-static» , подготовленной дружной командой проекта Интернет-технологии.ру