Pull to refresh

Ускоряем раздачу фоток

Reading time 8 min
Views 14K

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

Проявляется это приблизительно так: иногда 3Kb картинка грузится так, как будто бы она весит 3Mb, на ровном месте начинают «залипать» (отдаваться очень медленно) css-ы и JavaScript-ы. Вы нажимаете ctrl + reload — и уже, вроде, проблемы нет, потом спустя всего несколько минут все повторяется опять.

Не всегда истинная причина «тормозов» очевидна и мы косо поглядываем то на nginx, то на хостера, то на «забитый» канал, то на «тормозной» или «глючный» браузер :)

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

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


Как понять, что хард тормозит?


В Linux проблемы со скоростью дисковой системы напрямую связаны с параметром iowait (процент простоя CPU в ожидании операций ввода/вывода). Для того, чтоб мониторить этот параметр есть несколько комманд: mpstat, iostat, sar. Я обычно запускаю iostat 5 (замеры будут производиться каждые 5 сек.)
Я спокоен за сервер, у которого средний показатель iowait до 0.5%. Скорее всего на Вашем сервере «раздачи» этот параметр будет выше. Есть смысл не откладывать оптимизацию, если iowait > 10% Ваша система тратит очень много времени на перемещение головок по винчестеру вместо чтения информации, это может приводить к «торможению» и других процессов на сервере.

Как быть с большим iowait?


Очевидно, что если уменьшить количество дисковых операций ввода/вывода, винчестеру стант легче и iowait упадет.
Вот несколько рекомендаций:
  • Отключите access_log
  • Отключите обновление даты последнего доступа к файлу и директории, также позвольте системе кешировать операции записи на диск. Для этого монтируем файловую систему со следующими опциями: async,noatime,barrier=0. ('barrier=0' неоправданный риск, если на этом же разделе находится база данных)
  • Можно увеличить таймаут между сбросом «грязных» буферов vm.dirty_writeback_centisecs в /etc/sysctl.conf. У меня установлено vm.dirty_writeback_centisecs = 15000
  • Вы случайно не забыли про директиву expires max?
  • Не лишним будет включить кеширование дескрипторов файлов.
  • Приминение клиентской оптимизации: css-спрайты, все css — в один файл, все js — в один файл

Это немного поможет и даст время продержаться до апгрейда. Если проект растет, iowait о себе скоро напомнит. :)

Апгрейдим железо


  • Доустанавливаем RAM
    Пожалуй, начать можно с оперативной памяти. Linux задействует всю «свободную» оперативную память и разместит там дисковый кеш.
  • Старый провереный RAID-массив
    Можно собрать програмный или аппаратный RAID из нескольких HDD. В некоторых случаях есть смысл увеличивать количество винчестеров, но при этом не собирать их в RAID (например раздача iso-образов диска, больших видео-файлов, ...)
  • Solid-state drive: попробуем что-то новенькое
    Ну и самый, на мой взгляд, дешевый вариант апгрейда — доустановить в систему один или несколько SSD дисков. Cегодня, как Вы уже догадались, речь пойдет именно об этом способе ускорения.

Абсолютно не повлияет на скорость раздачи апгрейд CPU, потому что «тормозит» не он! :)

Почему SSD


Полтора года назад, когда я писал статью «Тюнинг nginx», одним из предложенных мной вариантов ускорения nginx было использование SSD винчестера. Хабрасообщество сдержано проявляло интерес к этой технологии, была информация о возможном торможении со временем SSD и опасение за малое количество циклов перезаписи.
Совсем скоро после публикации той статьи в нашей компании появился Kingston SNE125-S2/64GB на базе SSD Intel x25e который по сегодняшний день используется на одном из самых нагруженых серверов «раздачи».

После года экспериментов проявилось ряд недостатков, о которых мне хотелось бы рассказать:
  • Рекламный трюк: если в рекламе SSD написано что Максимальная скорость чтения, 250 Мб/с, то это означает что средняя скорость чтения будет равняться ~ 75% (~ 190Мб/с) от заявленной максимальной. Уменя так было с MLC и SLC, дорогими и дешёвыми
  • Чем больше объем одного SSD, тем выше стомость 1Мб на этом диске
  • Большинство файловых систем не адаптированы к использованию на SSD и могут создавать неравномерную нагрузку по записи на диск
  • Только самые современные (и соответственно самые дорогие) RAID-конроллеры адаптированы к подключению к ним SSD
  • SSD все еще остается дорогой технологией

Почему я использую SSD:
  • Реклама не врет — seek-to-seek действительно стремится к 0. Что позволяет существенно уменьшить iowait при паралельной «раздаче» большого количества файлов.
  • Да, действительно, количество циклов перезаписи ограничено, но мы об этом знаем и можем минимизировать количество перезаписываемой информации с помощью методики, описаной чуть ниже
  • Сейчас уже доступны диски, использующие технология SLC (Single-Level Cell) с «умным» конроллером, количество циклов перезаписи у которых на порядок выше обычных MLC SSD
  • Современные файловые системы (например btrfs) уже умеют правильно работать с SSD
  • Как правило, кеширующий сервер требует небольшое количество места под кеш (у нас это 100-200G), что может уместиться на 1 SSD. Получается, что это существенно дешевле решения на базе аппаратного RAID-масива c несколькими SAS дисками


Настраиваем SSD кеш


Выбор файловой системы
Вначале эксперимента на Kingston SNE125-S2/64GB была установлена ext4. В интернете вы найдете множество рекомендаций как «отрубить» журналирование, даты последнего доступа к файлам, и т.д. Все работало идеально и длительное время. Самое главное, что не устраивало — при большом количестве мелких фотграфий 1-5K на 64G SSD помещалось меньше половины — ~20G. Я начал подозревать, что мой SSD используется не рационально.

Проапгрейдил ядро до 2.6.35 и решил попробовать (пока еще экспериментальную) btrfs, там есть возможность при монтировании указать, что монтируется ssd. Диск можно не разбивать на разделы, как это принято, а форматировать целиком.

Пример:
mkfs.btrfs /dev/sdb

При монтировании можно отключить множество фич, которые нам не понадобятся и включить компрессию файлов и метаданных. (На самом деле jpeg-и сжиматься не будут, btrfs умная, сжатию будут подвергаться только метаданные). Вот как выглядят моя строка монтирования в fstab (все одной строкой):

UUID=7db90cb2-8a57-42e3-86bc-013cc0bcb30e /var/www/ssd btrfs device=/dev/sdb,device=/dev/sdc,device=/dev/sdd,noatime,ssd,nobarrier,compress,nodatacow,nodatasum,noacl,notreelog 1 2

Узнать UUID отформатированного диска можно командой
blkid /dev/sdb


В итоге на диск «влезло» более 41G (в 2 раза больше, чем на ext4). При этом скорость раздачи не пострадала (т.к. iowait не увеличился).

Собираем RAID из SSD
Наступил такой момент, когда 64G SSD стало мало, захотелось собрать несколько SSD в один большой раздел и при этом было желание использовать не только дорогие SLC, но и обычные MLC SSD. Тут надо вставить немного теории:

Btrfs сохраняет на диске 3 типа данных: данные о самой файловой системой, адреса блоков метаданных (на диске ведется всегда 2 копии метаданных) и, собственно, сами данные (содержание файлов). Экспериментальным путем я установил, что в нашей структуре директорий «сжатые» метаданные занимают ~30% от всех данных раздела. Метаданные — это самый интенсивно изменяемый блок, т.к. любое добавление файла, перенос файла, изминение прав доступа влечет за собой перезапись блока метаданных. Область где хранятся просто данные перезаписывается реже. Вот мы и подошли к самой интересной возможности btrfs: это создавать софтовые RAID-масыви и указывать явным образом, на каких дисках сохранять данные на каких метаданные.

Пример:
mkfs.btrfs -m single /dev/sdc -d raid0 /dev/sdb /dev/sdd

в результате метаданные будут создаваться на /dev/sdc а данные на /dev/sdb и /dev/sdd, которые будут собраны в stripped raid. Мало того, к существующей системе можно подключать еще диски, выполнять балансировку данных и т.д.

Чтоб узнать UUID btrfs RAID-а запустите:
btrfs device scan

Внимание: особенность работы с btrfs-райдом: перед каждым монтироваем RAID-масива и после загрузки модуля btrfs необходимо запускать комманду: btrfs device scan. Для автоматического монтирования через fstab можно обойтись без 'btrfs device scan', добавив опции device в строку монтирования. Пример:
/dev/sdb     /mnt    btrfs    device=/dev/sdb,device=/dev/sdc,device=/dev/sdd,device=/dev/sde


Кеширование на nginx без proxy_cache


Я предполагаю, что у вас есть storage-сервер, на котором находится весь контент, на нем много места и обычные «неповоротливые» SATA винчестеры, которые не в состоянии держать большую нагрузку совместного доступа.
Между storage-сервером и пользователями сайта есть сервер «раздачи», задача которого снять нагрузку с storage-сервера и обеспечить бесперебойную «раздачу» статики любому количеству клиентов.

Установим на сервер раздачи один или несколько SSD с btrfs на борту. Сюда прямо напрашивается конфигурация nginx на базе proxy_cache. Но у нее есть несколько минусов для нашей сисемы:
  • proxy_cache при каждом перезапуске nginx начинает постепенно сканировать все содержимое кеша. Для нескольких сотен тысяч это вполне допустимо, но если в кеш мы ложим большое количество файлов то такое поведения nginx — неоправданный расход дисковых операций
  • для proxy_cache не существует родной системы «чистки» кеша, а сторонние модули позволяют чистить кеш только по одному файлу
  • есть небольшой overhead по расходу CPU, т.к. при каждой отдаче выполняется MD5-хеширование над строкой, заданной в директиве proxy_cache_key
  • Но самое важное для тас то, что proxy_cache не заботится о том чтоб кеш обновлялся с наименьшим количеством циклов перезаписи информации. Если файл «вылетает» из кеша, то он удаляется и, если запрашивается повторно, то повторно записывается в кеш

Мы возьмем на оворужение другой подход к кешированию. Идея промелькнула на одной из конференций по hiload. Создаем в разделе кеша 2 директории cache0 и cache1. Все файлы при проксировании сохраняем в cache0 (с помощью proxy_store). nginx заставляем проверять наличие файла (и отдавать файл клиенту) сначала в cache0 потом в cache1 и если файл не найден, идем на storage сервер за файлом, затем сохраняем его в cache0.
Через некоторое время (неделя/месяц/квартал) удаляем cache1, переименовываем cache0 в cache1 и создаем пустой cache0. Анализируем логи доступа к секции cache1 и те файлы, которые запрашиваються из этой секции перелинковываем в cache0.

Этот метод позволяет существенно сократить операции записи на SSD, т.к. перелинковка файла — это все же меньше, чем полная перезапись файла. Кроме того можно собрать рейд из нескольких SSD, 1 из которых будет SLC для метаданных и MLC SSD для обычных данных. (На нашей системе метаданные занимают приблизительно 30% от общего объема данных). При перелинковке будут перезаписаны только метаданные!

Пример конфигурации nginx

log_format cache0  '$request';
# ...
server {  
  expires max;

  location / {
    root /var/www/ssd/cache0/ ;
    try_files $uri @cache1;
    access_log off;
  }

  location @cache1 {
    root /var/www/ssd/cache1;
    try_files $uri @storage;
    access_log /var/www/log_nginx/img_access.log cache0;
  }

  location @storage {
    proxy_pass http://10.1.1.1:8080/$request_uri;
    proxy_store on;
    proxy_store_access user:rw  group:rw  all:r;
    proxy_temp_path /var/www/img_temp/; # обязательно не на SSD!
    root /var/www/ssd/cache0/;
    access_log off;
  }
# ...


Скрипты для ротации cache0 и cache1
Я написал на bash несколько скриптов, которые помогут вам реализовать ранее описанную схему ротации. Если размер вашего кеша измеряется сотнями гигабайт а количество контента в кеше миллионами, то скрипт ria_ssd_cache_mover.sh сразу после ротации желательно запускать несколько раз подряд следующей командой:

for i in `seq 1 10`; do ria_ssd_cache_mover.sh; done;
Время, за которое выполнится эта команда установите експериментаольно. У меня она работала почти сутки. На сл. сутки устанавливаем запуск ria_ssd_cache_mover.sh на cron-е каждый час.

Защита от DOS-а storage server-а
Если storage server хиловат, и есть недоброжелатели, жаждущие задосить Вашу систему, можно совместно с описанным решением применить модуль secure_link

Полезные ссылки




UPD1: Все же советую использовать ядро >= 2.6.37 и старше, т.к. у меня на 2.6.35 недавно произошел большой креш кеша из-за переполнения места на SSD с метаданными. В результате пришлость форматировать несколько SSD и заново собирать btrfs-рейд. :(

Tags:
Hubs:
+124
Comments 69
Comments Comments 69

Articles