понедельник, 2 января 2017 г.

Оптимизация Squid 3+: Часть I

Введение

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

С учетом вышесказанного, возникла необходимость обобщить этот опыт и знания и немного поделиться им. 

Как и во всех случаях, дисклеймер:

Волшебной пули не существует.

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

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

Начиная оптимизацию, вы должны все это понимать и четко отдавать себе отчет, что вы можете достичь, а чего нет. А также - когда следует остановиться. 

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

Итак, постараюсь связно изложить все, что знаю.

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

  1. Функциональная оптимизация. Иными словами - все должно работать по-возможности правильно. Стабильность входит сюда же, если система работает нестабильно или требует постоянного внимания человека - все остальное не имеет значения.
  2. Оптимизация производительности. Сюда входит время отклика, отсутствие перегрузок CPU, эффективность используемых хелперов и некоторые другие показатели.
  3. Оптимизация byte hit. По принципу - чем выше, тем эффективней работает кэш. Ранее я уже объяснял, почему именно этот показатель имеет значение.

Для понимания всего вышесказанного, следует начать с архитектуры Squid 3+.


Архитектура

Первое, что вам необходимо помнить.

Squid был написан во времена, когда CMT машин не существовало. Это означает, что, за очень немногим исключением (aufs), в нем нет поддержки потоков. Это однопоточный продукт, который в лучшем случае - и частично - может функционировать в режиме SMP. То есть мультипроцессирования.

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

Чтобы сгладить однопоточность, основная работа по обработке запросов выполняется процессом-координатором и некоторым количеством дочерних процессов. Кроме того, имеются ограниченные возможности по выполнению запросов прокси-серверм в режиме pipeline, и некоторые другие возможности по оптимизации.

Отсутствие полноценной поддержки CMT (причем разработчики устно заявляют, что ее и не планируется в обозримом будущем) также означает, что продукт достаточно неудовлетворительно (за немногими весьма специфичными исключениями) работает в виртуализированных средах (VMWare, итп.) и следует предпочитать его выполнение на bare metal для достижения приемлемых показателей.

Физически режим SMP в Squid существует, однако он реализован не в полном объеме, имеет большое количество ограничений реализации и для полноценного применения в крупных инсталляциях фактически непригоден. Он завязан на использование rock, в реальности ограниченной на работу с мелкими файлами, в 2016 году это серьезное ограничение; также режим требует большого количества тюнинговых операций, кроме того, не все данные могут быть эффективно поделены между SMP-обработчиками, начиная с кэша в оперативной памяти.

Использование оперативной памяти

Память cache_mem используется для хранения наиболее горячих объектов не слишком большой величины. Существует распространенное заблуждение, что, если собрать Squid в 64 битах, и если сервер имеет много RAM, то cache_mem можно задавать сколь угодно неограниченно большим.

Это ошибка.

Исторически Squid не рассчитан на гигантский кэш в оперативной памяти. На большинстве систем адресация пространства свыше 4 Гб вызывает проблемы или обложена массой ограничений. Кроме того, нубы ошибочно считают, что гигантский кэш в памяти дает гигантский выигрыш. Это не так. После определенного объема скорость произвольного доступа падает, и, в какой-то момент, становится меньше, чем у жесткого диска. Парадокс большого кэша.

В-третьих, большинство системных библиотек управления памятью на ОС, произросших из настольных систем x86, физически не предусматривают, что сервер может иметь память больше, чем 4-8 Гб. Что означает непредсказуемое поведение в любой момент времени.

Не надо считать разработчиков геями-инопланетянами. У пианистов просто не всегда есть 48-юнитная стойка, переполненная процессорами и модулями памяти. Точнее сказать, такой стойки почти никогда нет.

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

Делается это от незнания того, для чего, собственно, эти пулы используются. Это замена обычного библиотечного/системного аллокатора собственным pool-based аллокатором. Фактически на некоторых ОС память, распределяемая системным аллокатором, выделяется слишком мелкими фрагментами и, в процессе работы, ее количество уменьшается со временем. Пулы позволяют кэшу выделить себе относительно непрерывное пространство под heap, и удерживать его выделенным (подобно TEMP). Если ваша ОС - кустарное самодельное дерьмо - это может быть лучшей стратегией, чем позволить ОС самой выделять и освобождать память (фрагментация оперативной памяти почти не лечится - только перезагрузкой - и может привести к свопингу). Размер memory_pools_limit тюнится пропорционально загрузке памяти прокси. Разработчики говорят - резонно - что размер этого параметра мало влияет на фактическое потребление памяти кэшом (что правда), а также, что можно задавать этот параметр резонно высоким, даже превосходящим размер физической памяти (я бы поостерегся это делать). Обычно потребление памяти происходит даже не за счет cache_mem, а за счет кэширования индексов свопа и, таким образом, прямо пропорционально заполнению дискового кэша. Для примера, сервер с дисковым кэшем, заполненным до ~160 Гб, заполнит оперативную память (после прогрева) примерно до 800-1000 Мб.

Хорошим начальным приближением для memory_pools_limit является 1/4 от размера физической памяти с последующим тюнингом по месту (чаще всего в сторону увеличения).

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

- Почему Squid жрет так много оперативной памяти (top показывает величину, в разы превосходящую cache_mem) и этот расход памяти постоянно увеличивается?

Ответ: Память, потребляемая прокси, складывается из следующих элементов:
  1. Собственно память, необходимая для запуска и выполнения процесса-координатора и всех кидов, включая процессы IO.
  2. cache_mem
  3. Кэширование индексных файлов всех cache_dir (swap.state). Обычно больше, чем совокупный объем всех swap.state-файлов. Что, в общем_ естественно. Именно за счет этой части происходит прирост потребляемой оперативной памяти. Кэш (а Squid - в первую очередь именно кэш, что бы ни заявляли сейчас его разработчики) по умолчанию пытается любой новый файл прежде всего сохранить на диске. Соответственно, постоянно добавляются новые entries в индексы, и, соответственно, их надо кэшировать в оперативной памяти, чтобы дисковый IO не был совсем уж удручающе медленным и печальным. Как говорит документация, в среднем соотношение составляет 15-17 мегабайт на каждый гигабайт потребленного дискового пространства. В действительности эти цифры весьма ориентировочны, и зависят от массы факторов, как самой ОС и fs, так и параметров собственно Squid. Вам следует иметь в виду эти расчеты при выборе размеров дискового хранилища под кэш и ограничителей размеров cache_dir, которые косвенно влияют на величину потребной оперативной памяти. Нубы часто ставят терабайтные диски и пытаются превратить кэш в arcive.org. И еще быстрой работы ожидают. Это огромная ошибка. У вас мочи не хватит сделать архив всего интернета. Не сейчас. Не в 2016 году.
Еще одна распространенная ошибка нубов, это установка параметра maximum_object_size_in_memory в высокие величины. Не делайте этого. Вы просто выбросите огромные куски оперативной памяти под гигантские файлы, которые, как правило, редко запрашиваются. Производительности это не добавит, более того, это приведет к проблемам, причем очень и очень скоро.

Не в качестве руководства к действию и не для бездумного копирования приведу фрагмент конфигурации оптимизированного кэша:

 # -------------------------------------  
 # Memory parameters  
 # -------------------------------------  
 cache_mem 512 Mb  
   
 memory_pools_limit 1024 MB  
   
 maximum_object_size_in_memory 1 MB  
   
 # -------------------------------------  
 # Store parameters  
 # -------------------------------------  
 minimum_object_size 10 bytes  
 maximum_object_size 4 Gb  
   
 cache_dir diskd /data/cache/d1 32767 64 512  
 cache_dir diskd /data/cache/d2 32767 64 512  
 cache_dir diskd /data/cache/d3 32767 64 512  
 cache_dir diskd /data/cache/d4 32767 64 512  
   
 # Default is 20  
 store_objects_per_bucket 32  
   
 store_avg_object_size 200 KB  
   

Машина, на которой задана такая конфигурация, имеет 8 ядер CPU, 4 Гб оперативной памяти, и диски SAS 15K RPM. Приведенные выше параметры относительно оптимальны. Говоря простым языком: хватамбо! Как видите, под дисковый кэш выделено 128 Гб. Для нагрузки порядка 500 пользователей этого более, чем достаточно - с учетом специфики нагрузки данного конкретного сервера. Я намеренно не буду вдаваться в детали этой нагрузки и остальных настроек, так как у вас все равно будет все иначе.

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