среда, 11 января 2017 г.

Сборка OpenSSH версии 7.3+ на Solaris 10

Коллеги-соляристы уже, думаю, обратили внимание, что, начиная с версии 7.3, OpenSSH невозможно собрать на Solaris 10 в 64 бита. И сидят на старье - что небезопасно, либо гоняют 32-битный код на 64-битном ядре, пользуясь дуальностью ABI.

На самом деле - невозможно собрать без плясок с бубном. Мне удалось получить решение от разработчиков (причем они сами его не считают нужным вкатить в исходники по религиозным соображениям), и я считаю нужным поделиться этим решением (оно вам пригодится).

Сперва вам нужны GNU patch, autoconf и automake. Их можно установить с OpenCSW, если вы этого до сих пор не сделали.

Затем нужны патчи.

Для версии 7.3 (7.3_solaris10_build_issue.patch):
 --- configure.ac     Thu Jul 28 04:54:27 2016  
 +++ configure.ac     Wed Aug 3 00:05:35 2016  
 @@ -751,6 +751,9 @@  
       use_pie=auto  
       check_for_libcrypt_later=1  
       check_for_openpty_ctty_bug=1  
 +     dnl Target SUSv3/POSIX.1-2001 plus BSD specifics.  
 +     dnl _DEFAULT_SOURCE is the new name for _BSD_SOURCE  
 +     CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE"  
       AC_DEFINE([PAM_TTY_KLUDGE], [1],  
            [Work around problematic Linux PAM modules handling of PAM_TTY])  
       AC_DEFINE([LOCKED_PASSWD_PREFIX], ["!"],  
 @@ -1790,11 +1793,8 @@  
       warn \  
  ])  
    
 -dnl Wide character support. Linux man page says it needs _XOPEN_SOURCE.  
 -saved_CFLAGS="$CFLAGS"  
 -CFLAGS="$CFLAGS -D_XOPEN_SOURCE"  
 +dnl Wide character support.  
  AC_CHECK_FUNCS([mblen mbtowc nl_langinfo wcwidth])  
 -CFLAGS="$saved_CFLAGS"  
    
  AC_LINK_IFELSE(  
      [AC_LANG_PROGRAM(  
   

Для версии 7.4 (7.4_solaris10_build_issue.patch):
 --- configure.ac     Mon Dec 19 21:18:36 2016  
 +++ configure.ac     Mon Dec 19 21:18:40 2016  
 @@ -740,6 +740,9 @@  
       use_pie=auto  
       check_for_libcrypt_later=1  
       check_for_openpty_ctty_bug=1  
 +     dnl Target SUSv3/POSIX.1-2001 plus BSD specifics.  
 +     dnl _DEFAULT_SOURCE is the new name for _BSD_SOURCE  
 +     CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE"  
       AC_DEFINE([PAM_TTY_KLUDGE], [1],  
            [Work around problematic Linux PAM modules handling of PAM_TTY])  
       AC_DEFINE([LOCKED_PASSWD_PREFIX], ["!"],  
 @@ -1771,11 +1774,8 @@  
       warn \  
  ])  
    
 -dnl Wide character support. Linux man page says it needs _XOPEN_SOURCE.  
 -saved_CFLAGS="$CFLAGS"  
 -CFLAGS="$CFLAGS -D_XOPEN_SOURCE"  
 +dnl Wide character support.  
  AC_CHECK_FUNCS([mblen mbtowc nl_langinfo wcwidth])  
 -CFLAGS="$saved_CFLAGS"  
    
  TEST_SSH_UTF8=${TEST_SSH_UTF8:=yes}  
  AC_MSG_CHECKING([for utf8 locale support])  
   

Патч нужно положить в корень дерева исходников OpenSSH и применить:

 root$server /tmp/openssh-7.4.p1 # patch -p0<7.4_solaris10_build_issue.patch   

Затем, в этой же директории, выполнить autoreconf. Если команда выполнится без ошибок - готово, можно приступать к конфигурированию и компиляции.

Безопасности вашему серверу!

PS. Чтобы autoreconf прошел без ошибок, нужно установить пакеты CSWautoconf, CSWautomake, CSWgpatch - если вы используете OpenCSW.

UPDATE

Начиная с версии OpenSSH 7.5 баг исправлен:


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

вторник, 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 почти максимально возможную производительность.

Использование aufs возможно (и иногда это приводит к ощутимому выигрышу), однако только после тщательного тестирования и на ваш страх и риск. Я хочу сказать, что не всякая платформа в принципе позволяет использовать aufs безнаказанно. Кроме того, aufs использует много больше файловых дескрипторов, что потребует специальной конфигурации лимитов ОС.

Что касается rock, то он недоработан и, на мой взгляд, совершенно непригоден к применению в продуктивных установках. Помимо того, что он не сочетается в одном инстансе с non-SMP-aware disk IO.

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

Сетевая подсистема 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, так и в хелперах - немедленно сказываются на общей латентности прокси и его производительности, и оказывают взаимное влияние на повременную статистику друг друга. Имейте это в виду при мониторинге и тюнинге.

воскресенье, 8 января 2017 г.

C++: Кто быстрее - слон или кит?

Не холивара ради, не нравоучений для, а просто для прикола. Небольшой кусочек сравнительной анатомии из жизни инопланетян-геев :) Ну или геев-инопланетян, кому как больше нравится.

Как вы думаете, мои хитиновые хвостатые, с рекурсивными челюстями, сошедшие с иллюстраций Гигера, друзья: какая реализация главного цикла из приведенных ниже быстрее выполняется? Нет, не трогая компилятор :)

Вариант I:

 // If max threads not specified, set default  
 std::thread threads[v_max_threads];  
 /*-------------Main Loop----------------*/  
      while (!std::cin.eof()) {  
           // Start all processing threads  
           for (auto& t: threads) {  
                t = std::thread([&]() {  
                     processdata();  
                     });  
           }  
           // Finish all threads  
           for (auto& t: threads) {  
                t.join();  
           }  
      }  
 /*-------------Main Loop----------------*/  
   

Вариант II

 // If max threads not specified, set default  
 std::vector<std::thread> threads;  
 threads.reserve(v_max_threads);  
 /*-------------Main Loop----------------*/  
      while (!std::cin.eof()) {  
           // Start all processing threads  
           for (unsigned int i = 0; i < v_max_threads; i++) {  
                threads.emplace_back(processdata);  
           }  
           // Finish all threads  
           for (auto& t: threads) {  
                t.join();  
           }  
           threads.clear();  
      }  
 /*-------------Main Loop----------------*/  
   

Вариант III

 // If max threads not specified, set default  
 std::thread* threads = new std::thread[v_max_threads];  
 /*-------------Main Loop----------------*/  
      while (!std::cin.eof()) {  
           // Start all processing threads  
           for (unsigned int i = 0; i < v_max_threads; i++) {  
                threads[i] = std::thread([&]() {  
                     processdata();  
                     });  
           }  
           // Finish all threads  
           for (unsigned int i = 0; i < v_max_threads; i++) {  
                threads[i].join();  
           }  
      }  
 /*-------------Main Loop----------------*/  
 delete [] threads;  
   

Сдаетесь? :)

Профайлер говорит (и сравнительный бенчмарк подтверждает): "скорость практически одинакова, плюс-минус миллисекунда".

При этом вариант II генерирует примерно на 1 Кб больше кода (в байтах). (Компилятор GCC 5.2 в режиме -std=c++11, разумеется, режим оптимизации -O3). Вариант III совпадает до байта по генерации кода с вариантом I, который в C++ возможен, однако опасен (компилятор не во всяком режиме вообще позволит собрать такое). При этом, вариант III еще небезопасен тем, что возможны утечки памяти, если произойдет исключение до оператора delete [] .

Соответственно, из вышеперечисленных соображений "правила пальца" выбираем предпочтительным вариант II. :)

PS. Предварительная оптимизация - корень всех зол, да. Любим, помним, скорбим. :)

понедельник, 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 пользователей этого более, чем достаточно - с учетом специфики нагрузки данного конкретного сервера. Я намеренно не буду вдаваться в детали этой нагрузки и остальных настроек, так как у вас все равно будет все иначе.

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