Раздача статического контента — счет на милисекунды

image

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

И вот мы решили ускорять то, что и так работает быстро и, заодно, поделиться опытом того, что получилось в итоге. Конечно же я расскажу о граблях, о том где не надо HTTP/2, о том почему мы покупаем 7,6Tb NVMe SSD вместо 8×1Tb SATA SSD и много другой узкоспециализированной информации.

Давайте сразу договоримся что хранение и раздача контента это 2 разные задачи и говорить мы будем только про раздачу (продвинутый кеш)

Начнем с железа…

NVMe SSD


Как вы уже поняли, мы будем идти в ногу с прогрессом и задействуем для хранения кеша современный 7.68 TB SSD HGST Ultrastar SN260 {HUSMR7676BHP3Y1} NVMe, HH-HL AIC. Тесты винта показывают не такую красивую картинку как в маркетинговых матерриалах, но вполне оптимистичную

[root@4 www]# hdparm -Tt --direct /dev/nvme1n1

/dev/nvme1n1:
 Timing O_DIRECT cached reads:   2688 MB in  2.00 seconds = 1345.24 MB/sec
 Timing O_DIRECT disk reads: 4672 MB in  3.00 seconds = 1557.00 MB/sec

[root@4 www]# hdparm -Tt /dev/nvme1n1

/dev/nvme1n1:
 Timing cached reads:   18850 MB in  1.99 seconds = 9452.39 MB/sec
 Timing buffered disk reads: 4156 MB in  3.00 seconds = 1385.08 MB/sec


Конечно же размер и производителя подбираете «под себя», можно рассматривать и SATA интерфейс, но мы же пишем о том к чему нужно стремиться :)

Если вы все же остановили свой выбор на NVMe, для получения инфо о винте установим пакет nvme-cli и смотрим характеристики нашей «рабочей лошадки»

[root@4 www]# nvme smart-log /dev/nvme1n1
Smart Log for NVME device:nvme1n1 namespace-id:ffffffff
critical_warning                               : 0
temperature                         : 35 C
available_spare                     : 100%
available_spare_threshold           : 10%
percentage_used                     : 0%
data_units_read                     : 158 231 244
data_units_written                  : 297 968
host_read_commands                  : 45 809 892
host_write_commands                 : 990 836
controller_busy_time                : 337
power_cycles                        : 18
power_on_hours                      : 127
unsafe_shutdowns                    : 14
media_errors                        : 0
num_err_log_entries                 : 10
Warning Temperature Time            : 0
Critical Composite Temperature Time : 0
Temperature Sensor 1                : 35 C
Temperature Sensor 2                : 27 C
Temperature Sensor 3                : 33 C
Temperature Sensor 4                : 35 C


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

В пиковые периоды ми раздаем около 4000/фото в секунду (размер фото около 10–100K), на половине такой нагрузки iostat не поднимается более 0.1%, там еще RAM играет большое значение, но об этом еще напишу)

Несколько слов о том, почему сейчас мы делаем ставку на дорогие NVMe, вместо мешка дешевых SATA SSD. Мы провели тесты, которые показывают, что при схожей архитектуре сервера, объемом RAM и одинаковой нагрузке, например, Ultrastar SN260 7.68TB NVMe работает с в 10 раз меньшим iowait-ом чем 8xSamsung SSD 850 PRO 1TB в Stripped RAID с Areca ARC-1882 Raid Controller PCI. На серверах есть небольшое отличие в кол-ве ядер c NVMe 26, с ARC-1882 24 ядра, количество RAM 128G там и там. К сожалению, нет возможности сравнить энергопотребление на этих серверах. Представилась возможность измерить энергопотребление NVMe платформы с аналогичной по назначению системой на процах AMD с программным Stripped RAID из 8xINTEL SSDSC2BB480G4 480G и контроллером ARC-1680 Raid Controller PCI на 24 ядрах AMD Opteron Processor 6174, при одинаковой нагрузке, новая система «жрет» в 2.5 раза меньше энергии 113 Watts против 274 Watts на AMD. Ну и загрузка CPU и iowait там тоже на порядок меньше (на AMD нет аппаратного шифрования)

Файловая система


8 лет назад мы использовали btrfs, пробовали еще XFS, но ext4 на большой параллельной нагрузке ведет себя живее, так что наш выбор ext4. Правильная настройка ext4, может дополнительно повысить и без того отличную производительность этой fs.
Оптимизация начинается с момента форматирования, например, если вы, в основном, раздаете файлы 1–5K, можно немного уменьшить размер блока при форматировании:

mkfs.ext4 -b 2048 /dev/sda1 


или даже

mkfs.ext4 -b 1024 /dev/sda1


Для того чтоб узнать текущий размер блока на файловой системе используйте:

tune2fs -l /dev/sda1 | grep Block


или

[root@4 www]# fdisk -l /dev/nvme1n1

Диск /dev/nvme1n1: 7681,5 ГБ, 7681501126656 байтів, 1875366486 секторів
Одиниці = секторів з 1 * 4096 = 4096 байтів
Розмір сектора (логічного/фізичного): 4096 байтів / 4096 байтів
Розмір введення-виведення (мінімальний/оптимальний): 4096 байтів / 4096 байтів
Тип мітки диска: dos
Ідентифікатор диска: 0x00000000


Так как на нашем NVMe SSD размер сектора 4k, то размер блока делать меньше этого значения неоптимально, форматируем:

mkfs.ext4 /dev/nvme1n1


Обратите внимание, я не разбивал диск на разделы, а форматирую его целым блочным устройством. Для OS я использую другой SSD и там разбивка есть. В файловую систему его можно подмонтировать как /dev/nvme1n1

Монтировать кеш диск желательно таким образом, чтоб выжать из него максимум по скорости, для этого все ненужное отключаем, мы будем монтировать с опцией «noatime, barrier=0», если вам атрибут atime важен, в ядре 4 и выше есть опция lazytime, она держит значение atime в ОЗУ и частично решает проблему частых обновлений времени доступа при чтении.

RAM


Если ваша статика помещается в оперативную память, забудьте про все вышенаписанное и получайте удовольствие от раздачи с файлов с ОЗУ.

При раздаче с ОЗУ, я не надеюсь на ОС, которая помещает в память то, что считает нужным и монтирую раздел со статикой на RAM-диск, который в линуксе создается следующей командой:

mount -t tmpfs -o size=1G,nr_inodes=10k,mode=0700,noatime tmpfs /cache


если вы забыли с какими параметрами монтировали, то можно посмотреть с помощью findmnt:

findmnt --target /cache


Можно перемонтировать без перезагрузки:

mount -o remount,size=4G,nr_inodes=40k,noatime /cache


Можно также комбинировать часть в RAM (какие-нибудь часто часто запрашиваемые превьюшки) остальное на SSD.

В nginx это будет выглядеть как-то так:

 location / {
    root /var/cache/ram;
    try_files $uri @cache1;
 }

 location @cache1 {
    root /var/cache/ssd;
    try_files $uri @storage;
 }


Если контент в RAM не влезает, придется довериться ядру, мы ставим 128G RAM при размере активного кеша 3–5G и подумываем про увеличение до 256G.

CPU


Особых требований к частоте процессора нет, скорее есть требование к функциональности: если ваш трафик необходимо будет шифровать (например, по протоколу https), важно выбрать такой процессор, который будет поддерживать аппаратное шифрование AES-NI (Intel Advanced Encryption Standard).

В Linux проверить поддержку процессором инструкций AES-NI можно командой

grep -m1 -o aes /proc/cpuinfo
aes


Если нет вывода «aes», то процессор такие инструкции не поддерживает и шифрование будет пожирать производительность процессора.

Настраиваем nginx


Общие идеи описаны в предыдущей статье, но все же есть несколько моментов по оптимизации, про них раскажу сейчас:

  • Мы избегаем реврайтов на верхнем уровне директивы server, не приветствуем регулярные выражения в секциях location. Если без этого никак, то постарайтесь хотя бы обернуть регулярку в обычный location, например:
            location /example/dir {
                rewrite ^/example/dir(.*) /newexample/$1;
            }
    

    Если этого не сделать, то регулярка будет применяться для каждого запроса к системе раздачи, а в нашем примере примерка регулярки редиректа будет происходить только в случае, когда путь к фото начинается на /example/dir
  • При хранении контента в кеше придерживайтесь правила не хратить более 255 файлов или папок в одной папке (если форматировали диск с настройками «по-умолчанию» с размером блока 4K), 128 если 2K и т.д.
  • Ставим новое ядро, желательно не ниже 4.1. В nginx не забываем включить поддержку SO_REUSEPORT Эта директива положительно влияет на параллельную загрузку файлов с сервера раздачи.
  • Размещаем сервер поближе к вашим пользователям, таким образом мы делаем счастливее пользователей и про это знают и это ценят поисковые системы


Как насчет CDN


CDN это хорошо, если вы работаете на разные страны, у вас много контента и мало трафика, но если трафика много, контента мало, и вы работаете на конкретную страну, то есть смысл все просчитать и понять, что Вам выгоднее. Например, мы работем на Украинский рынок, у многих мировых лидеров, предоставляющих сервис CDN, серверов в Украине нет и раздача идет с Германии или Польши. Вот и получаем вместо +3–5ms — +30–50ms к ответу на ровном месте. Размещение сервера 2U в хорошем украинском ДЦ стартует от 18$ + оплата за канал, например, 100Mbps — 10$ итого 28$. Коммерческая цена раздачи CDN-трафика в Украине около 0.05$/G, т.е. если раздаем более 560G/месяц, то уже можно рассматривать вариант самостоятельной раздачи. Сервисы RIA.com раздают несколько террабайт статики в день, и поэтому мы уже давно приняли решения о самостоятельной раздаче.

Как дружить с поисковиками


Для многих поисковиков важными характеристиками являются TTFB (time to first byte) и то насколько «близко» контент расположен к тому, кто его ищет, кроме того, важны тексты в ссылках на контент, описание в Exif-тегах, уникальность, размер контента и т.д.
Все о чем, я здесь пишу, в основном для того, чтоб протюнить TTFB и быть поближе к пользователю. Можно пойти на хитрость и по User-Agent-у детектить поисковых ботов и отдавать для них контент с отдельного сервера чтоб исключить «заторы» или «притормаживания в пиковые периоды» (обычно боты дают равномерную нагрузку), таким образом делая счастливыми не пользователя, а поисковики. Мы так не делаем, кроме того есть подозрение что Гугл Хром и Яндекс Браузер больше доверяют той информации, которую им поставляют браузеры о скорости загрузки Ваших страниц с позиции клиента.

Также стоит отметить, что нагрузка от разных ботов может быть настолько существенной, что вам придется тратить чуть ли не половину ресурсов на обслуживание этих ботов. Проекты RIA.com обслуживают около 10–15 миллионов запросов от ботов с сутки (сюда включены обращения не только к статике, но и к обычным страницам) это не намного меньше, чем количество запросов от реальных пользователей.

Оптимизация для раздачи статического контента


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

  • Первое, на что стоит обратить внимание — это формат фото: оказывается, что jpeg, который очень популярен для многих проектов уже уступает по размеру при сравнимом качестве более новым форматам в Web, например формат WebP который продвигает компания Google с 2010 года. По разным данным получаем на 20–30% меньше размер, при равном качестве. На клиенте при этом можно использовать также специальный тег , который позволяет описать в себе несколько форматов, которые может отобразить браузер и в случае если браузер не поддерживает формат WebP он загрузит, например, jpeg
    Также немного о SEO требованиях:
    • Часть SEO-оптимизации вы уже сделали тем, что разместили сервер раздачи поближе к клиенту и ускорили его отдачу
    • Несколько слов о exif-тегах, многие их вырезают при масштабировании фото — напрасно! Гугл тоже анализирует эту информацию, почему бы не указать для фото то, что изображено на фото в exif-теге ImageDescription, или в exif-теге Copyright не вписать авторские права на свой контент, если, конечно он Ваш :)
    • Не забывайте про HTTP-заголовки, в которых указывается разная полезная мета-информация о файле контента. Например, с помощью заголовка Expires, можно указать время хранения контента в кеше браузера.

    Если ваш сайт работает по протоколу HTTP/2, то стоит проэкспериментировать со следующими оптимизациями:
    • Вы можете отказаться от использования css-спрайтов, так как мультиплексирование передачи маленьких файлов по одному соединению может компенсировать выиграш, который обычно достигается по http/1.1 при использовании этой оптимизации
    • попробуйте технологию web-push, обратите внимание, что такой метод оптимизации загрузки не учитывает наличие в кеше браузера контента, который пушится, но это решается использованием куков и несложной настройкой nginx, как показано в примере
      server {
          listen 443 ssl http2 default_server;
      
          ssl_certificate ssl/certificate.pem;
          ssl_certificate_key ssl/key.pem;
      
          root /var/www/html;
          http2_push_preload on;
      
          location = /demo.html {
              add_header Set-Cookie "session=1";
              add_header Link $resources;
          }
      }
      
      map $http_cookie $resources {
          "~*session=1" "";
          default "; as=style; rel=preload, ; as=image; rel=preload, 
                   ; as=style; rel=preload";
      }
      

    • Из нашего опыта, раздачу контента лучше осуществлять с именем домена отличным от основного домена проекта.

    Загадочный HTTP/2


    Как вы знаете HTTP/2 мультиплексирует одно соединение, через которое передается несколько файлов, при этом возможна приоретизация в отправке того или иного файла, сжатие заголовков и другие плюсы нового протокола, но есть и минусы, о которых мало кто пишет. Зайду издалека: может есть кто постарше из хабражителей, кто помнит интернет до эпохи uTorrent, многим из вас приходилось использовать специальные качалки flashget, download master и др. Помните, как они работали? Они качали один файл в 6 или 8 потоков, открывая 6–8 соединений с отдающим сервером. Почему они так делали? Ведь канал с раздающим и принимающим сервером не должен зависеть от количества соединений между ними, но на самом деле эта зависимость есть, если канал плохой, с потерей пакетов и ошибками передачи пакетов. Оказывается, что при таких раскладах, в несколько потоков качается быстрее. Кроме того если канал используется несколькими клиентами то увеличение количества соединений от одного клиента помогает получать больше полосу в канале и перетягивать «одеяло ресурсов» на себя. Конечно, так происходит не всегда, но все же есть угроза получить «загребущего» конкурента в виде браузера работающего по протоколу http/1.1, который откроет 6 конекшинов к одному сайту, вместо 1 по http2. В моей практике был случай с сайтом типа «фотохостинг обоев для рабочего стола», который отказался от http/2, по той причине что сайт на http/2 протоколе заметно тормозил, без видимой нагрузки на самом сервере, ребята остались на https, но перешли на http/1.1 и ситуация разрешилась.

    Я бы еще поэкспериментировал с получением контента с разных доменов (но фактически с одного сервера), этот прием называется domain sharding, возможно как можно было выйти из ситуации не отказываясь от http/2 и принуждая устанавливать браузеру столько соединений сколько нужно администратору сайта.

    Вместо заключения


    Тот момент, когда сайт начинает подтормаживать нельзя назвать неприятным, ведь вы чувствуете что трафик растет, клиентов становиться больше. Мы никогда не ставили перед собой задачу избежать «тормозов», мы учились быстро реагировать на этот рост. Оптимизация быстродействия — это процесс бесконечный, не отказывайте себе в удовольствии посоревноваться со своими конкурентами в искусстве быть быстрыми!

© Habrahabr.ru