понедельник, 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 Гб вызывает проблемы или обложена массой ограничений. Кроме того, нубы ошибочно считают, что гигантский кэш в памяти дает гигантский выигрыш. Это не так. После определенного объема скорость произвольного доступа падает, и, в какой-то момент, становится меньше, чем у жесткого диска. Парадокс большого кэша. Сам Squid внутри себя не содержит эффективных механизмов адресации действительно больших объемов оперативной памяти.

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

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

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

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

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

Хорошим начальным приближением для memory_pools_limit является 1/5-1/10 от размера cache_mem с последующим тюнингом по месту и нагрузке. Не более, и уж, конечно, не сообразно размеру cache_mem.

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

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

Ответ: Память, потребляемая прокси, складывается из следующих элементов:
  1. Собственно память, необходимая для запуска и выполнения процесса-координатора и всех кидов, включая процессы IO.
  2. cache_mem
  3. memory_pools_limit - причем следует помнить, что этот лимит может быть кратковременно превышен
  4. Кэширование индексных файлов всех 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 50 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 пользователей этого более, чем достаточно - с учетом специфики нагрузки данного конкретного сервера. Я намеренно не буду вдаваться в детали этой нагрузки и остальных настроек, так как у вас все равно будет все иначе.

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