вторник, 10 января 2017 г.

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

Архитектура (продолжение)

Дисковый IO

Дисковый IO, за исключением aufs (реализация которого подходит исключительно для Linux и оставляет желать лучшего), реализован как 1 или более процессов. Неблокирующим IO является aufs, diskd и rock (применяемый исключительно в SMP-конфигурациях с множеством ограничений).

Технически aufs использует треды и должен бы по идее работать несколько быстрее diskd, особенно на CMT-машинах. Однако реальных бенчмарков не приводилось, а на ряде платформ, к тому же, невозможна или бессмысленна замена diskd на aufs. Кроме того, по многочисленным сообщениям, aufs нередко приводит к падению disk hit и множественным ошибкам об отсутствии кэшированного файла (в cache.log), прямо приводящим к перезагрузке файла из интернета.

Несмотря на возможные скоростные проблемы, таким образом, единственным прилично отлаженным механизмом неблокирующего дискового IO является diskd (кстати, rock реализует в точности такой же механизм IO и позиционируется как высокоскоростной дисковый кэш). 

Небольшой тюнинг и правильное планирование СХД позволяет получить с применением diskd почти максимально возможную производительность.

Сетевая подсистема

Сетевая подсистема Squid достаточно архаична. 

С одной стороны, она использует платформенно-специфичные механизмы поллинга - SELECT/POLL/EPOLL/dev/poll.

С другой - она также не является многопоточной, не поддерживает - и не будет поддерживать, по словам разработчиков - libevent и event ports.

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

Из имеющихся средств масштабирования стоит упомянуть только pipeline_prefetch (предварительная выборка и пре-парсинг некоторого количества запросов с упреждением в некую очередь - попытка спародировать предвыборку Chrome, достаточно неэффективная) и collapsed_forwarding - а это совсем из другой оперы, это группировка однотипных запросов к одному ресурсу, отдаваемому с заголовками Range Request и Partial Content, в предположении, что, возможно, эти куски можно кэшировать (это приводит к увеличению задержек при обработке запросов, на момент написания статьи полностью не отлажено и не доработано, и против TCP_MISS/206 практически неэффективно, как показывают тесты).

Фактически, разработчики прямо подталкивают в ситуации, требующей масштабирования, к применению SMP (действительно, могущего увеличить нагрузочную способность одиночного инстанса, однако этот функционал опоздал по меньшей мере лет на 5-10), однако ввиду его крайней сырости, платформенной специфичности, высокой сложности в конфигурировании и применении, я врагу не пожелаю его использовать.

Так как разработчики публично отказываются перерабатывать архитектуру на thread-aware по причинам, которые им кажутся объективными, единственная полноценная возможность масштабирования - это построение кластеров parent-child-sibling с распределением входящего трафика по инстансам.

Хелперы

Одно из самых узких и спорных мест в Squid. 

Первое, что вам необходимо помнить. Внутренняя архитектура хелперов - сериальная. То есть ни о каком сколько-нибудь выраженном параллелизме речь не идет. Из особенностей следует упомянуть лишь очереди запросов к хелперам (concurrency; в версии 4+ расширены добавлением достаточно длинных очередей ожидающих обработки запросов queue-size), и крайне кривой механизм распределения запросов по дочерним процессам хелперов - это совсем не round-robin или least-load, распределение на практике чрезвычайно неравномерное, приводящее к перегрузке первого из процессов, вне зависимости от степени загруженности остальных процессов, и почти во всех случаях статус BUSY для этого первого процесса. С соответствующими задержками в обработке. Возможно, ситуация будет исправлена в версии 5+ - было анонсировано изменение алгоритма распределения запросов по хелперам. Посмотрим.

Следует правильно интерпретировать смысл параметра concurrency для дочерних процессов хелперов. Со стороны squid это не более, чем нумерованная channel ID сериальная очередь к каждому из процессов.

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

Ну вот так вот, просто. Все имеющиеся в настоящий момент на рынке хелперы, за исключением ufdbguard 1.32.5 beta 5 и DCB, не являются многопоточными. От слова "совсем". Даже если способны работать с concurrency>1.

Истинно параллельный (или thread-aware) хелпер должен сразу же, на стыке с Squid, для каждого запроса, поступающего с отдельным channel-ID, стартовать отдельный тред (или процесс) для обработки.

Если он это не делает - он однопоточный.

С точки зрения масштабирования, сейчас, в 2016м году, каждый хелпер должен быть с возможностью параллельной обработки очередей. Это, однако осложняется технической реализацией потоков IO на интерфейсе хелперов и относительной сложностью программирования многопоточных приложений, использующих cin/cout. Кроме того, многие разработчики не считают целесообразным утруждать себя подобным трудом, не видя очевидной выгоды от подобной реализации (кстати, ошибочно).

Что необходимо держать в голове в связи с хелперами. 

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