Автономная работа frontend (заглушка, proxy_store, use_stale)

    Введение


    Технические работы неожиданно случаются у всех проектов и площадок — избежать нельзя, можно только подготовиться. В этом обзоре собран наш опыт перевода front фермы на автономный режим работы — без хранилища и backend.
    • заглушка
    • proxy_store
    • proxy_cache_use_stale + memcache ttl=0



    1. Заглушка


    Это самый простой способ — положить статичную страничку на локальный диск по всей ферме и настроить rewrite на нее всех запросов.

    server {
      listen 80;
      location / {
        rewrite ^.*$ /maintance.html;
      }
      location /maintance.html {
        alias .../maintance.html;
        expires -1;
      }
    }
    


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

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


    upd: В комментариях предложили еще использовать директиву try_files, которая решает эту задачу без rewrite и разных location. И ставить expires заведомо в прошлое, чтобы браузер не закешировал у себя заглушку.

    1.5

    Долго так продолжаться конечно же не могло и, перед следующим плановым downtime на 8 часов, нам поставили задачу
    откручивать рекламу любой ценой

    Для понимания масштаба — у нас в сутки около полутора миллионов уников на двух сотнях новостных проектов, десятки (близко к ста) миллионов хитов на разный контент на front ферму, большая часть графики и видео лежит на CDN. Front ферма состоит из трех nginx узлов, над которыми стоит аппаратный балансировщик.

    2. proxy_store


    На время проведения работ в ферму был включен nginx-night со следующими настройками
    • балансировщик отправлял на него четверть запросов пользователей
    • в качестве upstream по всем проектам выступали три основных узла фермы
    • все проходящие ответы записывались на SSD-массив с помощью директивы nginx proxy_store


    location / {
      proxy_pass              http://nginx-farm;
      proxy_set_header        Host    $host;
      proxy_set_header		X-Forwarded-For $remote_addr;
      proxy_pass_header	X-Accel-Redirect;
      proxy_pass_header	X-Accel-Expires;
      proxy_ignore_headers	X-Accel-Redirect;
      set $store_path         ---$request_uri---$query_string;
      if ($store_path ~ "(.*)(.{1})(.{2})"){
        set $new_store_path $3/$2/$store_path;
      }
      proxy_store             /data/cache/store/$host/$new_store_path;
    }
    
    location ~ \.(flv|asf|mp4)$ {
      proxy_pass              http://nginx-farm;
      proxy_set_header        Host    $host;
      proxy_set_header        X-Real-IP       $remote_addr;
    }
    


    В час Х основные узлы были выведены из балансировки, конфиг nginx-night изменен на примерно такой

    location / {
      root /data/cache/store/$host/;
      set $store_path ---$request_uri---$query_string;
      if ($store_path ~ "(.*)(.{1})(.{2})"){
        set $new_store_path $3/$2/$store_path;
      }
      rewrite ^.*$ /$new_store_path break;
      expires 1m;
    }
    
    location ~ css {
      default_type text/css;
      root /data/cache/store/$host/;
      set $store_path ---$request_uri---$query_string;
      if ($store_path ~ "(.*)(.{1})(.{2})"){
        set $new_store_path $3/$2/$store_path;
      }
      rewrite ^.*$ /$new_store_path break;
      expires 1m;
    }
    


    По сравнению с заглушкой это был огромный шаг вперед, но
    • 700 MB/sec — именно такой была скорость записи на массив в режиме накопления данных
    • нет возможности сохранять статусы ответов и заголовки
    • для обработки user friendly urls, разбиения страниц на подкаталоги пришлось разбивать uri регуляркой и дописывать query_string — это исключает возможность определения content type для большинства сохраненных файлов (именно для этого пришлось уже в боевом режиме вводить дополнительный location для css)
    • никто не гарантирует, что 1 url = 1 страница (персонализированные блоки, pjax)
    • при накоплении больше 200GB SSD-массив начинал уходить в себя


    upd: Вообще-то здесь правильней было использовать proxy_cache, но у нас была всего одна неделя от «показать рекламу» до «выключаем рубильник» и мы сделали выбор в пользу пусть ущербного, но гарантированного решения.

    2.5

    По итогам у нас даже было желание решить найденные проблемы самописным решением, но на глобальные задачи не набралось приоритета, а площадка тем временем успела немного измениться
    • локальные кеши легли на ramdisk, размеры проектных кешей за счет этого увеличились более чем на порядок, inactive выставлен в десять часов
    • чтение статики с хранилища вынесено на отдельный пул процессов nginx на каждом узле, раздающий проксирует запросы на него, для мелкой статики настроен локальный кеш на ramdisk
    • на нескольких узлах memcache организован глобальный кеш, управляемый приложениями

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

    3. proxy_cache_use_stale


    На всех проектах выставлен
    proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
    

    Директива указывает nginx отдавать из кеша данные в случае ошибок, даже если данный элемент кеша уже считается устаревшим.
    Дополнительно к этому
    • на nginx узлах подняты memcache, все проекты, пишущие данные в memcache, дополнительно записывали их в эти instance с ttl=0 (бесконечным временем жизни)
    • наведен некоторый порядок в проектных конфигах — все настройки upstream (app и memcache) вынесены в отдельные файлы
    • для персонализированных блоков предусмотрена отдача пустышки при недоступности backend

    И далее идет штатная эксплуатация всей площадки до момента Х, когда проводятся следующие манипуляции
    • опускаем пул процессов, читающих с хранилища — мелкая статика, не попавшая в CDN, отдается с кеша по use_stale
    • переписываем app upstream's на несуществующий локальный порт и ставим ему connection_timeout 5ms, снова работает use_stale
    • переписываем адреса memcache, эта часть работает штатно

    Во время учений перед работами техподдержка радовала закрытием тикетов фразой «жалоб со стороны пользователей не поступало». В боевом режиме — девять часов автономной работы, схема оправдала все ожидания — новости читались, видео смотрелось, реклама крутилась. Хотя конечно и здесь нашлись некоторые проблемы
    • потребовались некоторые изменения в приложениях
    • часть проектов у нас делают no-cache, некоторые из них из-за ошибок в конфигурациях
    • локальность кешей не гарантирует наличие свежих статей на всех узлах, пользователи натыкались на 404-ые и 502-ые
    • отдельно стоят малопосещаемые проекты, их кеши перед такими работами следует прогревать
    • по прежнему не имеем защиты от некорректных данных в кешах


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

    3.5

    Для следующего шага основной целью станет научиться говорить «ДА» в ситуации «все пропало, нам срочно надо откатить проект на 15/30 минут назад, можете ли вы это сделать, а мы пока исправим причины» )

    proxy_store
    proxy_cache_use_stale
    proxy_cache_path
    Memcache::set
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 13

      +1
      Используем 1-е решение, поскольку извращённых задач, вроде открутки рекламы на периоды даунтайма нам не ставят. Обнаружили баг — некоторые браузеры кешируют заглушку и «отпускают» только по CTRL+F5. Поэтому лучше добавить add_header Expires «1970-01-01 00:00:00» и/или правильный Cache-Control.
        0
        Спасибо за добавление, добавил expires -1; на location.
        0
        Касательно первого примера (заглушка). Почему rewrite, а не try_files?
          0
          rewrite привычней, ссылку на try_files сейчас добавлю, спасибо
          0
          А почему в пункте 2 proxy_store, а не proxy_cache?
            0
            Proxy_store гарантирует запись в хранилище, независимо от заголовков, управляющих поведением кеша. Это позволило нам не делать подробный аудит всех проектов на площадке, на который не было времени. По большому счету это был выбор в пользу гарантированного взлета в ущерб правильности решения.
              0
              Ага, понятно тогда. Сначала увидился, прочитав про несохранение заголовков, потом увидел proxy_store, решил вот поинтересоваться :)

              Кэш на трёх фронтах никак не синхронизируете, они в этом плане полностью независимы?
                0
                Родные nginx кеши не синхронизируем, одна из причин Общий proxy cache для нескольких фронтенд-серверов. Вместо этого у нас есть глобальный (общий для всех серверов) кеш в memcache.
                  0
                  Ну, эту проблему как раз можно решить, если интересно — готов прислать патчик, правда под старую версию.
                    0
                    Вторая причина — у нас несколько сотен тысяч файлов в кешах, пока пройдет синхронизация половина из них уже перестанет быть валидными. А единый кеш на несколько серверов это spof.
                      0
                      Не, я патчик делает распределённый кэш, если одна нода вылетит — просто потеряется часть ключей. Или, если места не жалко, на каждой ноде копию хранить. Никакого SPOF, никакой синхронизации файликов по-отдельности.
                        0
                        Распределенный кеш? Это интересно ) maksim@woyager.ru
            0
            Что за 200 новостных сайтов? Сетки вроде readme.ru?

            Only users with full accounts can post comments. Log in, please.