Nginx cache: всё новое — хорошо забытое старое

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

    Но что делать, когда ваш код “идеален”, все тяжелые запросы вынесены в фон, все, что можно, было закэшировано, а сервер все так же не дотягивает до нужных нам показателей SLA? Если есть возможность, то конечно можно докупить новых машин, распределить часть трафика и забыть о проблеме еще на некоторое время.

    Но если вас не покидает чувство, что ваш сервер способен на большее, или есть магический параметр, ускоряющий работу сайта в 100 раз, то можно вспомнить о встроенной возможности nginx, позволяющей кэшировать ответы от бэкенда. Давайте разберем по порядку, что это, и как это может помочь увеличить количество обрабатываемых запросов сервером.

    Что такое Nginx cache и как он работает?


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

    Перед тем как приступить к конфигурации nginx, необходимо убедиться, что он собран с модулем “ngx_http_proxy_module”, так как с помощью этого модуля мы и будем производить настройку.

    Для удобства можно вынести конфигурацию в отдельный файл, например “/etc/nginx/conf.d/cache.conf”. Давайте рассмотрим директиву “proxy_cache_path”, которая позволяет настроить параметры хранения кэша.

    proxy_cache_path /var/lib/nginx/proxy_cache levels=1:2 keys_zone=proxy_cache:15m max_size=1G;

    “/var/lib/nginx/proxy_cache” указывает путь хранения кэша на сервере. Именно в эту директорию nginx будет сохранять те самые файлы с ответом от бэкенда. При этом nginx не будет самостоятельно создавать директорию под кэш, об этом необходимо позаботиться самому.

    “levels=1:2” — задает уровень вложенности директорий с кэшем. Уровни вложенности указываются через “:”, в данном случае будет созданы 2 директории, всего допустимо 3 уровня вложенности. Для каждого уровня вложенности доступны значения от 1 до 2, указывающие, как формировать имя директории.

    Важным моментом является то, что имя директории выбирается не рандомно, а создается на основе имени файла. Имя файла в свою очередь является результатом функции md5 от ключа кэша, ключ кэша мы рассмотрим чуть позже.

    Давайте посмотрим на практике, как строится путь до файла кэша:

    /var/lib/nginx/proxy_cache/2/49/07edcfe6974569ab4da6634ad4e5d492

    “keys_zone=proxy_cache:15m” параметр задает имя зоны в разделяемой памяти, где хранятся все активные ключи и информация по ним. Через “:” указывается размер выделяемой памяти в Мб. Как заявляет nginx, 1 Мб достаточно для хранения 8 тыс. ключей.

    “max_size=1G” определяет максимальный размер кэша для всех страниц, при превышении которого nginx сам позаботится об удалении менее востребованных данных.

    Также есть возможность управлять временем жизни данных в кэше, для этого достаточно определить параметр “inactive” директивы “proxy_cache_path”, который по умолчанию равен 10 минутам. Если в течение заданного в параметре “inactive” времени к данным кэша не было обращений, то эти данные удаляются, даже если кэш еще не “скис”.

    Что же из себя представляет этот кэш? На самом деле это обычный файл на сервере, в содержимое которого записывается:

    • ключ кэша;
    • заголовки кэша;
    • содержимое ответ от бэкенда.

    Если с заголовками и ответом от бэкенда все понятно, то к “ключу кэша” есть ряд вопросов. Как он строится и как им можно управлять?

    Для описания шаблона построения ключа кэша в nginx существует директива “proxy_cache_key”, в которой в качестве параметра указывается строка. Строка может состоять из любых переменных, доступных в nginx.

    Например:

    proxy_cache_key $request_method$host$orig_uri:$cookie_some_cookie:$arg_some_arg;

    Символ “:” между параметром куки и get-параметром используется для предотвращения коллизий между ключами кэша, вы можете выбрать любой другой символ на ваше усмотрение. По умолчанию nginx использует следующую строку для формирования ключа:

    proxy_cache_key $scheme$proxy_host$request_uri;

    Следует отметить следующие директивы, которые помогут более гибко управлять кэшированием:

    proxy_cache_valid — Задает время кэширования ответа. Возможно указать конкретный статус ответа, например 200, 302, 404 и т.д., либо указать сразу все, с помощью конструкции “any”. В случае указания только времени кэширования, nginx по дефолту будет кэшировать только 200, 301 и 302 статусы.

    Пример:

    proxy_cache_valid 15m;
    proxy_cache_valid 404 15s;
    

    В этом примере мы установили время жизни кэша в 15 минут, для статусов 200, 301, 302 (их nginx использует по умолчанию, так как мы не указали конкретный статус). Следующей строчкой установили время кэширования в 15 секунд, только для ответов со статусом 404.

    proxy_cache_lock — Эта директива поможет избежать сразу нескольких проходов на бэкенд за набором кэша, достаточно установить значение в положении “on”. Все остальные запросы будут ожидать появления ответа в кэше, либо таймаут блокировки запроса к странице. Соответственно, все таймауты возможно настроить.

    proxy_cache_lock_age — Позволяет установить лимит времени ожидания ответа от сервера, после чего на него будет отправлен следующий запрос за набором кэша. По умолчанию равен 5 секундам.

    proxy_cache_lock_timeout — Задает время ожидания блокировки, после чего запрос будет передан на бэкенд, но ответ не будет закэширован. По умолчанию равен 5 секундам.

    proxy_cache_use_stale — Еще одна полезная директива, позволяющая настроить, при каких случаях возможно использовать устаревший кэш.

    Пример:

    proxy_cache_use_stale error timeout updating;

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

    proxy_cache_bypass — Задает условия, при которых nginx не станет брать ответ из кэша, а сразу перенаправит запрос на бэкенд. Если хотя бы один из параметров не пустой и не равен “0”. Пример:

    proxy_cache_bypass $cookie_nocache $arg_nocache;

    proxy_no_cache — Задает условие при котором nginx не станет сохранять ответ от бэкенда в кэш. Принцип работы такой же как у директивы “proxy_cache_bypass”.

    Возможные проблемы при кэшировании страниц


    Как уже упоминалось выше, вместе с кешированием HTTP ответа nginx сохраняет полученные от бэкенда заголовки. Если на вашем сайте используется сессия, то сессионная кука также будет закэширована. Все пользователи, зашедшие на страницу, которую вам посчастливилось закэшировать, получат ваши персональные данные, хранящиеся в сессии.

    Следующая задача, с которой придется столкнуться — это управление кэшированием. Конечно можно установить незначительное время кэша в 2-5 минут и этого будет достаточно в большинстве случаев. Но не во всех ситуациях такое применимо, поэтому будем изобретать свой велосипед. Теперь обо всем по порядку.

    Управление сохранением cookie

    Кэширование на стороне nginx накладывает некоторые ограничения на разработку. Например, мы не можем использовать сессии на закэшированных страницах, так как пользователь не доходит до бэкенда, еще одним ограничением будет отдача cookies бэкендом. Так как nginx кэширует все заголовки, то чтобы избежать сохранения чужой сессии в кэше, нам нужно запретить отдачу cookies для кэшируемых страниц. В этом нам поможет директива “proxy_ignore_headers”. В качестве аргумента перечисляются заголовки, которые должны быть игнорированы от бэкенда.

    Пример:

    proxy_ignore_headers "Set-Cookie";

    Этой строкой мы игнорируем установку cookies с проксируемого сервера, то есть пользователь получит ответ без заголовка “Set-Cookies”. Соответственно все, что бэкенд попытался записать в cookie, будет проигнорировано на стороне клиента, так как он даже не узнает, что ему что-то предназначалось. Это ограничение в установке cookie следует учесть при разработке приложения. Например для запроса авторизации можно отключить игнорирование заголовка, чтобы пользователь получил сессионную куку.

    Также следует учитывать время жизни сессии, его можно посмотреть в параметре “session.gc_maxlifetime” конфига php.ini. Представим, что пользователь авторизовался на сайте и приступил к просмотру новостной ленты, все данные при этом уже есть в nginx кэше. Через некоторое время пользователь замечает, что его авторизация пропала и ему снова нужно проходить процесс авторизации, хотя все это время он находился на сайте, просматривая новости. Это произошло потому, что на все его запросы nginx отдавал результат из кэша, не передавая запрос на бэкенд. Поэтому бэкенд решил, что пользователь неактивен и спустя время указанное в “session.gc_maxlifetime” удалил файл сессии.

    Чтобы этого не происходило, мы можем эмулировать запросы на бэкенд. Например через ajax посылать запрос, который будет гарантированно проходить на бэкенд. Чтобы пройти на бэкенд мимо nginx кэша, достаточно отправить POST запрос, также можно использовать правило из директивы “proxy_cache_bypass”, либо просто отключить кэш для этой страницы. Запрос не обязательно должен что-то отдавать, это может быть файл с единственной строкой, стартующей сессию. Цель такого запроса — продлить время жизни сессии, пока пользователь находится на сайте, и на всего его запросы nginx добросовестно отдает закэшированные данные.

    Управление сбросом кэша

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

    Перед тем как начать писать свое решение, посмотрим, что предлагает nginx из “коробки”. Для сброса кэша в nginx предусмотрена специальная директива “proxy_cache_purge”, в которой записывается условие сброса кэша. Условие на самом деле является обычной строкой, которая при непустом и не “0” значении удалит кэш по переданному ключу. Рассмотрим небольшой пример.

    proxy_cache_path /data/nginx/cache keys_zone=cache_zone:10m;
    
    map $request_method $purge_method {
        PURGE   1;
        default 0;
    }
    
    server {
        ...
        location / {
            proxy_pass http://backend;
            proxy_cache cache_zone;
            proxy_cache_key $uri;
            proxy_cache_purge $purge_method;
        }
    }
    

    Пример взят с официального сайта nginx.

    За сброс кэша отвечает переменная $purge_method, которая является условием для директивы “proxy_cache_purge” и по дефолту установлена в “0”. Это означает, что nginx работает в “обычном” режиме (сохраняет ответы от бэкенда). Но если изменить метод запроса на “PURGE”, то вместо проксирования запроса на бэкенд с сохранением ответа будет произведено удаление записи в кэше по соответствующему ключу кэширования. Также возможно указать маску удаления, указывая знак “*” на конце ключа кэширования. Тем самым нам не нужно знать расположения кэша на диске и принцип формирования ключа, nginx берет на себя эти обязанности. Но есть и минусы этого подхода.

    • Директива “proxy_cache_purge” доступна как часть коммерческой подписки
    • Возможно только точечное удаление кэша, либо по маске вида {ключ кэша}“*”

    Так как адреса кэшируемых страниц могут быть совершенно разными, без общих частей, то подход с маской “*” и директивой “proxy_cache_purge” нам не подходит. Остается вспомнить немного теории и открыть любимую ide.

    Мы знаем, что nginx кэш — это обычный файл на сервере. Директорию для хранения файлов кэша мы самостоятельно указали в директиве “proxy_cache_path”, даже логику формирования пути до файла от этой директории мы указали с помощью “levels”. Единственное, чего нам не хватает, это правильного формирования ключа кэширования. Но и его мы можем подсмотреть в директиве “proxy_cache_key”. Теперь все что нам остается сделать это:

    • сформировать полный путь до страницы, в точности как это указано в директиве “proxy_cache_key”;
    • закодировать полученную строку в md5;
    • сформировать вложенные директории пользуясь правилом из параметра “levels”.
    • И вот у нас уже есть полный путь до файла кэша не сервере. Теперь все, что нам остается, это удалить этот самый файл. Из вводной части мы знаем, что nginx может быть расположен не на машине приложения, поэтому необходимо заложить возможность удалять сразу несколько адресов. Снова опишем алгоритм:
    • Сформированные пути к файлам кэша мы будем записывать в файл;
    • Напишем простой сценарий на bash, который поместим на машину с приложением. Его задачей будет подключиться по ssh к серверу, где у нас находится кэширующий nginx и удалить все файлы кэша, указанные в сформированном файле из шага 1;

    Перейдем от теории к практике, напишем небольшой пример, иллюстрирующий наш алгоритм работы.

    Шаг 1. Формирование файла с путями до кэша.

    $urls = [
        'httpGETdomain.ru/news/111/1:2',
        'httpGETdomain.ru/news/112/3:4',
    ];
    
    function to_nginx_cache_path(url) {
    	
        $nginxHash = md5($url);
        $firstDir = substr($nginxHash, -1, 1);
        $secondDir = substr($nginxHash, -3, 2);
    
        return "/var/lib/nginx/proxy_cache/$firstDir/$secondDir/$nginxHash";
    }
    
    // Создаем файл с уникальным именем в директории tmp
    $filePath = tempnam('tmp', 'nginx_cache_');
    
    // Открываем созданный файл на запись
    $fileStream = fopen($filePath, 'a');
    
    foreach ($urls as $url)
    {
        // Собираем путь до файла кэша
        $cachePath = to_nginx_cache_path($url);
    
        // Построчно записываем путь до файла кэша
        fwrite($fileStream, $cachePath . PHP_EOL);
    }
    
    // Закрываем открытый дескриптор файла
    fclose($fileStream);
    
    // Вызываем bash скрипт с файлом в качестве аргумента 
    exec("/usr/local/bin/cache_remover $filePath");
    

    Обратите внимание, что в переменной “$urls” содержатся url закэшированных страниц, уже в формате “proxy_cache_key”, указанном в конфиге nginx. Url выступает неким тегом для выводимых сущностей на странице. Например, можно создать обычную таблицу в бд, где каждый сущности будет сопоставлена конкретная страница, на которой она выводится. Тогда при изменении каких-либо данных мы можем сделать выборку по таблице и удалить кэш всех необходимых нам страниц.

    Шаг 2. Подключение на кэширующий сервер и удаление файлов кэша.

    # Объединяем содержимое файла в одну строку, с пробелом в качестве разделителя
    FILE_LIST=`cat $1 | tr "\n" " "`
    
    # путь до ssh команды
    SSH=`which ssh`
    
    USER="root" # Логин под кем будем заходить на машину с nginx
    HOST="10.10.1.0" # Хост подключения
    KEY="/var/keys/id_rsa" # SSH ключ, так как мы будем использовать авторизацию не по паролю 
    
    $SSH -i ${KEY} ${USER}@${HOST} "rm -f ${FILE_LIST}" # Подключение на сервер и выполнение команды rm -rf
    
    rm -f $1 # Удаление файла 
    

    Приведенные примеры несут ознакомительный характер, не стоит использовать их в production. В примерах опущены проверки входных параметров и ограничения команд. Одна из проблем с которой можно столкнутся — это ограничение длины аргумента команды “rm”. При тестировании в dev окружении на небольших объемах это можно легко упустить, а в production получить ошибку “rm: Argument list too long”.

    Кэширование персонализированных блоков


    Давайте подведем итог, что нам удалось сделать:

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

    Но не все так хорошо, как может показаться на первый взгляд. Сейчас, наверное, если не у каждого первого, то точно у каждого второго сайта есть функционал регистрации/авторизации, после прохождения которых, мы захотим вывести имя пользователя где-нибудь в шапке. Блок с именем, является уникальным и должен отображать имя пользователя, под которым мы авторизованы. Так как nginx сохраняет ответ от бэкенда, а в случае со страницей — это html содержимое страницы, то и блок с персональными данными также будет закэширован. Все посетители сайта будут видеть имя первого пользователя прошедшего на бэкенд за набором кэша.
    Следовательно, бэкенд не должен отдавать блоки в которых находится персональная информация, чтобы эта информация не попала под nginx кэш.

    Нужно рассмотреть альтернативную подгрузку таких частей страницы. Как всегда это можно сделать множеством способов, например после загрузки страницы отправлять ajax запрос, а на месте персонального контента отображать лоадер. Другим способом, который мы как раз сегодня и рассмотрим, будет использование ssi тегов. Давайте вначале разберемся что из себя представляет SSI, а затем, как мы можем его использовать в связке с nginx кэшем.

    Что такое SSI и как он работает


    SSI (Server-Side Includes, включения на стороне сервера) — это некий набор команд, встраиваемых в html страницу, указывающие серверу, что нужно сделать.

    Вот некоторый перечень таких команд (директив):

    • if/elif/else/endif — Оператор ветвления;
    • echo — Выводит значения переменных;
    • include — Позволяет вставлять содержимое другого файла в документ.
    Как раз о последней директиве и пойдет речь. Директива include имеет два параметра:
    • file — Указывает путь к файлу на сервере. Относительно текущей директории;
    • virtual — Указывает виртуальный путь к документу на сервере.

    Нас интересует параметр “virtual”, так как указывать полный путь до файла на сервере не всегда удобно, либо в случае распределенной архитектуры файла на сервере попросту нет. Пример директивы:

    <!--#include virtual="/user/personal_news/"-->

    Для того, чтобы nginx начал обрабатывать ssi вставки, необходимо модифицировать location следующим образом:

    location / {
        ssi on;
        ...
    } 
    

    Теперь все запросы, обрабатываемые location “/”, будут иметь возможность выполнять ssi вставки.

    Как же во всей этой схеме будет проходить наш запрос?

    • клиент запрашивает страницу;
    • Nginx проксирует запрос на бэкенд;
    • бэкенд отдает страницу с ssi вставками;
    • результат сохраняется в кэш;
    • Nginx “дозапрашивает” недостающие блоки;
    • итоговая страница отправляется клиенту.

    Как видно из шагов, в nginx кэш попадет ssi конструкции, что позволит не кэшировать персональные блоки, а клиенту будет отправлена уже готовая html страница со всеми вставками. Вот наша подгрузка работает, nginx самостоятельно запрашивает недостающие блоки страницы. Но как и у любого другого решения этот подход имеет свои плюсы и минусы. Представим, что на странице есть несколько блоков, которые должны отображаться по-разному в зависимости от пользователя, тогда каждый такой блок будет заменен на ssi вставку. Nginx, как и ожидалось, запросит каждый такой блок с бэкенда, то есть один запрос от пользователя породит сразу несколько запросов на бэкенд, чего совсем бы не хотелось.

    Избавляемся от постоянных запросов к бэкенду через ssi


    Для решения этой задачи нам поможет модуль nginx “ngx_http_memcached_module”. Модуль позволяет получать значения от сервера memcached. Записать через модуль не получится, об этом должен позаботиться сервер приложения. Рассмотрим небольшой пример настройки nginx в связке с модулем:

    server {
        location /page {
            set            $memcached_key "$uri";
            memcached_pass 127.0.0.1:11211;
            error_page     404 502 504 = @fallback;
        }
    
        location @fallback {
            proxy_pass     http://backend;
        }
    }
    

    В переменной $memcache_key мы указали ключ, по которому nginx попробует получить данные из memcache. Параметры подключения к серверу memcache задаются в директиве “memcached_pass”. Подключение можно указать несколькими способами:

    • Доменное имя;

    memcached_pass cache.domain.ru;

    • IP адрес и порт;

    memcached_pass localhost:11211;

    • unix сокет;

    memcached_pass unix:/tmp/memcached.socket;

    • upstream директива.

    upstream cachestream {
      hash $request_uri consistent;
      server 10.10.1.1:11211;
      server 10.10.1.2:11211;
    }
    
    location / {
        ...
        memcached_pass cachestream;
        ...
    }
    

    Если nginx удалось получить ответ от сервера кэша, то он отдает его клиенту. В случае когда данных в кэше нет, запрос будет передан на бэкенд через “@fallback”. Эта небольшая настройка memcached модуля под nginx поможет нам сократить количество проходящих запросов на бэкенд от ssi вставок.

    Надеемся, эта статья была полезна и нам удалось показать один из способов оптимизации нагрузки на сервер, рассмотреть базовые принципы настройки nginx кэширования и закрыть возникающие проблемы при его использовании.
    Поделиться публикацией

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

    Комментарии 27
      0
      Добрый день, вы каким либо образом мониторите эффективность работы и настроек кеша nginx?
      например увеличение времени жизни кеша 2 раза дает ли какой либо прирост?
        0
        Каких-то специальных метрик не снимали, эффективность работы можно проверить по графикам мониторинга — в нашем случае это zabbix.
        Из графиков интересны показатели «число активных процессов», «количество соединений в секунду», вашего бэкенда(fpm/apache). Соответственно, чем ниже показатели, тем лучше.

        Так как сбросом кэша управляете вы сами, можете смело увеличивать время кэширования.
        Главное не переусердствовать, чтобы на каждую новую запись не происходило вытеснение, лишние операции на файловую систему никому не нужны.

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

        Если же вам это не подходит, можно самостоятельно прогревать кэш, также можете посмотреть в сторону директивы «proxy_cache_background_update».
        –2
        А в чем смысл кэшировать nginx, а не redis, например?
          +1
          Nginx кэш — это не замена основному кэшированию(redis/memcached/и тд), а скорее дополнение.
          Наша задача — как можно меньше пропустить запросов на бэкенд.
          Меньше запросов — меньше нагрузка на сервер.

          Работа nginx как раз заключается в том, чтобы большая часть пришедших запросов получила заготовленные ответы от сервера. То есть nginx отдает ответ, не проваливаясь на бэкенд.

          В случае с redis все запросы проходят на бэкенд, и уже бэкенд, обращаясь к redis, формирует ответ.
          Если же вы имели в виду модуль «ngx_http_redis_module», то его мы не рассматривали.
          +2

          В свое время мы очень долго играли в игру "сделай кеш вместо разработчика".
          В итоге когда нам удалось их заставить переписать приложение по человечески — все проблемы магическим образом исчезли.
          Если не хотите по ночам разбираться почему вдруг анонимным пользователям показывает профиль (админа/другого юзера/подставить по вкусу) лучше вообще не лезть в эту сторону.

            0
            Ну, а что если использовать nginx cache для сервера который отдаёт статику: css/js/png/jpg/svg?
              +2

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

              0
              Вполне возможно избежать перемешивания сессий анонима и админа.
              В качестве примера можете посмотреть на проект Engintron. Это плагин к cPanel, который добавляет поддержку nginx.
              В этом примере можете посмотреть как исключить кеш для важных локаций поплуярных CMS:
              github.com/engintron/engintron/blob/master/nginx/proxy_params_dynamic
              И таким образом можно добавить в исключения то, что не должно кэшироваться.
                +1

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


                1. разработчики не знают о существовании http_cache_control и зачем это нужно
                2. request_uri постоянно меняются (удаляются/добавляются)
                3. http_cookie поменяли а вам ничего не сказали

                Метод вполне рабочий, но лучше таки идти путем исправления самого приложения
                Добавляя кеш ну 50% вы выжмете в сторону увеличения производительности
                А вот переписав код можно и 5000% — это вполне реальная цифра из личного опыта


                Кстати расскажите мне как будет производиться инвалидация кеша при N+1 серверах фронта?

              0
              Директива “proxy_cache_purge” доступна как часть коммерческой подписки
              а жаль, очень не хватает.
                +1

                Она работает и в обычной версии. Только видимо нельзя инвалидировать с помощью PURGE.
                У нас в проде используется такой подход:


                proxy_cache_key "$host$request_filename";
                ....
                        location ~ /drop(/.*) {
                            allow *.*.*.*/*;
                            deny all;
                            proxy_cache_purge images "$host/usr/share/nginx/html$1";
                        }

                И все успешно инвалидируется, достаточно дописать /drop в начало пути.

                0
                cat $1 | tr "\n" " "
                Антипаттерн. Команда cat вообще редко нужна в скриптах. Тут можно было бы написать так:
                xargs < "$1"
                  0
                  > Возможные проблемы при кэшировании страниц

                  Еще одна проблема (кроме описанных тут закэшированных кукисов) это кэширование документа, сжатого каким-то одним алгоритмом (например brotli), и выдача его по запросу клиента, который не поддерживает этот алгоритм и не передавал его в заголовке запроса accept-encoding.
                    +1
                    Vary: Accept-Encoding
                    0
                    Как верно подмечено, nginx хранит кеш в виде файлов. И тот же самый nginx точно также быстро отдает простые статические файлы. Поэтому, есть достаточно простой вариант, как обойтись без кеширования nginx. Ну или хотя бы частично.

                    Вот реальный пример этой идеи из продакшена проекта средней нагруженности:

                    Есть ресурс, который производит некоторые трансформации над изображениями, он расположен там же, на фронтенд сервере, где и стоит собственно nginx.

                    Приходит get запрос на отдачу картинки, nginx проксирует его к этому небольшому быстрому бекенду, в заголовки запроса он включает заголовок:

                    proxy_set_header X-IMAGE-CACHE-EXPIRY 31d; # s - sec, m - minute, h - hour, d - day

                    Бекенд проверяет время модификации файла, определяет актуальность ранее созданного файла на основе этого заголовка и отдает nginx-у либо 304, либо делает трансформацию и сохраняет свежий результат в файл с тем же именем. Nginx соотвественно, либо отдает 304, либо берет этот свежий файл и отдает его.

                    Теперь, если нам надо сбросить наш кеш для этой конкретной разновидности картинки, мы передаем этот header со значением "-1", либо если нужно сбросить весь кеш ставим в конфиге "-1", делаем рестарт nginx и ждем 5...10...15 минут. Точно также можно удалить весь наш локальный кеш, не останавливая nginx.

                    Методология хранения файлов в кеше самая простая, делаем MD5 из полного урла со всеми get параметрами, и создаем структуру хранения наших файлов:

                    /ab/cd/abcdxxxxxxxxxxxxxxxx
                      +1

                      Смысл в том, чтобы не вызывать backend в принципе, там где его вызов не нужен.


                      Если Вы держите кэш в redis, то это будет обращение от nginx к бэкэнду,
                      бэкэнд должен будет обратиться к redis. В любом случае, это лишние задержки и лишняя нагрузка и на redis, и на backend.


                      Отдача на стороне nginx исключает эти задержки. Он просто отдаст клиенту бинарный файл с кэшем ответа.

                        0
                        Для Nginx есть модули redis/redis2, и не нужно обращаться к бэкэнду, чтоб сходить в redis, редис и будет бэкэндом для nginx
                          0

                          В редисе что хранится? Готовый бинарный ответ? Тогда, в принципе, да, можно наверное даже выиграть в скорости на небольших проектах. Если там лежат сырые данные (хотяб и скэшированные) из которых нужно сделать ответ, то это лишний оверхед.

                            0
                            Там хранится ответ, в ключе по урлу. html/json/etc, контент собственно.
                            Оверхед на генерацию хидеров? Не смешите, из кеша nginx делает практически всю ту же работу, а если учесть еще вырезание заголовков кук, то еще больше.
                            По скорости оно кстати вполне ок, у нас 1 сервер выдает овер 10кк хитов html5 баннеров в сутки. И это виртуалка 1 ядро, 4 gb ram.
                        0

                        Если благодаря директиве proxy_cache_bypass nginx обратился к бекенду, то он обновит свой кэш новым ответом, или нет?

                          0
                          Да.
                            0
                            Все верно, если любой из параметров «proxy_cache_bypass» не пустота и не «0», тогда запрос отправится на бэкенд. При этом полученный результат будет сохранен в кэш.
                            Если вам не нужно сохранять ответ от севера в кэш, то используйте совместно с директивой «proxy_no_cache».
                            0
                            Тоже использую Nginx как быстрый кеширующий SSL реверс-прокси. Пробовал сначала Varnish, с ним гораздо проще, но увы, он не умеет в HTTPS.
                              0
                              proxy_ignore_headers — там же вроде все немного по другому, по дефолту nginx не кеширует с куками (что выглядит правильным — user-specific контент не закешируешь), а сейчас вы наоборот, заставляете это делать, и работа сайта может быть нарушена во многопользовательской среде?
                                +1
                                Боюсь, что в nginx кэш попадает весь результат, полученный от сервера — как содержимое, так и заголовки. Вся работа nginx заключается в том, чтобы собрать ключ страницы по правилу из «proxy_cache_key», проверить кэш, и если он валиден, вернуть его клиенту, либо вначале сходить на бэкенд.
                                Понятия уникальности контента пользователя для nginx нет. Поэтому все, что ему вернуть, он с радостью сохранит.
                                Директива «proxy_ignore_headers» помогает нам указать, какие заголовки не следует обрабатывать от бэкенда. Как следствие — nginx будет их игнорировать и клиенту они не дойдут.
                                  0
                                  Вы смешали действие директив proxy_ignore_headers и proxy_hide_header. Директива proxy_ignore_headers не уберет заголовок из ответа клиенту, а лишь предотвратит его влияние на кэшируемость ответа.

                                  Если вы хотити чтобы заголовок не передавался клиенту, то необходимо использовать директиву proxy_hide_header.
                                +1

                                Хороший туториал!
                                Тоже несколько лет назад приходилось настраивать кэширование на уровне nginx. Решили избавиться от генерации превьюшек при загрузке фото, т.к. очень часто они не нужны, а место занимают + когда требуется новый размер превью — либо городить костыли, либо генерирвать новый сет превьюшек из терабайтов фото…
                                Теперь актуальный набор превьюшек вмещается в ~60GB (ну экономия по приблизительным оценкам получилась минимум порядок), а то что отсутствует в кэше — уже проксируется на бэкенды и потом складывается в кэш на недельку.


                                Самое плохое что случалось — кэширование сессионных печенек, но это уже просто криворукость отдельного меня была :))


                                Ну и не хватает модуля для просмотра статистики кэширования (сколько ревалидаций, сколько промахов, сколько хитов...), пришлось самому писать, но использовали его только по началу, потом просто отпала необходимость в статистике, да и обнаруживались всякие странности в работе этого модуля. Может быть кому-нибудь пригодится для экспериментов: https://github.com/SakhCom/ngx_cache_status_module/

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

                                Самое читаемое