TheCacheWorks
52 subscribers
62 links
Power in knowledge
Download Telegram
​​Виртуализация не бесплатна

Причем вопреки этому и этому исследованию, она не бесплатна не только для x86, а вообще для любых процессорных архитектур.

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

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

Справедливости ради, заметим, что далеко не все задачи эффективно параллелятся. В этой связи вообще представляется нецелесообразным уход от модели SMP, в силу того, что единственным, по существу, способом хоть сколько-нибудь эффективной утилизации многоядерных ЦПУ (исключая небольшой процент вычислительных задач и задач потоковой обработки данных) является запуск большого числа слабосвязанных процессов. По сути, та самая виртуализация.

Увы - законы физики обойти невозможно и оверхед при виртуализации существует, существовал и существовать будет. Его не способны победить даже легковесные контейнеры.

Этот факт хорошо известен программистам, пишущим по-настоящему высокопроизводительный код; когда буквально пара лишних ассемблерных команд в горячем месте просаживает производительность на 20-80%.

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

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

И единственный выход из нее - это полный и безоговорочный отказ от порочной концепции TTM и возврат к медленной и мучительной разработке высококачественного (тредового) кода высококвалифицированными программистами.

Другого способа перестать греть воздух апокалиптическими ДЦ, обмолачивающими экстремально неэффективный код, просто не существует.

Несколько фактов на закуску. Эффективных стратегий MPP не существует, в силу данного факта многоядерные системы тонут в lock contention, о чем большинство не задумывается, не замечает и, зачастую, даже не догадывается. Само существование многоядерников (с по-настоящему большим количеством ядер) в принципе перпендикулярно концепции горизонтального масштабирования и закону Амдаля-Уэра и свидетельствует лишь о том, что никто не знает, как еще можно ускорить выполнение той массы предельно неэффективного кода, который уже написан. Переписывать все это, разумеется, не вариант. Отсюда это засилье виртуализации в сочетании с попытками попутно решить пару-тройку проблем, связанных с общей некомпетентностью. Судорожные попытки nVidia/AMD/Intel усидеть одновременно на двух стульях вертикального и горизонтального масштабирования, рожая странных уродцев в не менее странных архитектурах упираются одновременно в TDP и отсутствие качественной софтверной поддержки. Особенно живописно выглядит одноядерный турбобуст Интел и разнотипныe ядра Apple.

Индустрия в тупике, из которого теперь просто и легко не выйти. В лучшем случае - это будет страшно дорого и ужасно больно.
​​Данные, лежащие в облаке - не ваши данные

Удивительно, но до сих пор почти ни до кого так и не дошло, что данные, лежащие в облаке - не ваши данные, подобно тому, как сервер, стоящий где-то в датацентре - не ваш сервер.

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

По поводу серверов - известен лишь один вендор и лишь одна ОС (1, не сбейтесь со счета), позволяющая программно заблокировать USB- и прочие порты на подключение лишь одного внешнего устройства со вполне определенным серийным номером либо отключить порты полностью. И этот вендор и эта ОС не сказать, чтобы безумно популярны в наши дни. Помимо того, что об этом свойстве даже в кругу почитателей оного вендора мало кто знает. Это касательно вашего сервера вне вашего физического контроля.

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

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

Увы и ах, но "Не задумываясь" - истинный девиз индустрии.
​​Рак разъедающий

Легаси - это то, что мы не понимаем, понимать и поддерживать не хотим и вообще это NIH. А рефакторинг не нужен, родной!

В действительности, вы не туда смотрите. Фундамент метастазирования индустрии мало того, что заложен не вчера - он практически непобедим и болеет совсем не только ИТ.

Фундаментальных проблем две и они системные.

Первая - менеджмент. Целями менеджмента являются две вещи - больше хэдкаунта, больше бюджетов - больше значимости. Карьерный рост (менеджера), KPI, бонусы - вот это вот всё. Оптимизация - в смысле сокращения хэдкаунта и бюджетов - которая имеет смысл с точки зрения собственников - наемному менеджеру ортогональна. Собственники, однако, на другой планете живут - пока худо-бедно что-то крутится, прибыль - какая-никакая - идет - им глубочайшим образом плевать, что там под капотом творится. Надо купить полтысячи серверов? Да и пофиг, это всего лишь деньги. Пока их хватает. Оптимизация? Рефакторинг? Да на кой?! Тайм ту маркет - вот что важно, нужно и значимо. А пользователи - да куда они денутся-то, с подводной лодки. Все так делают. Рыночек выбирает между плохо и очень плохо.

Вторая - исполнители. То, что software engeneering это, в первую очередь, engeneering - всем плевать. Инженерный ВУЗ - не нужен. Он не учит тому, что надо исполнителям - а именно, ремесленничеству (всеобщий плачь Ярославны о том, почему в инженерных ВУЗах учат какой-то абстрактной ненужной хрени, а не нажимать кнопки и пользоваться инструментами - привет, МУИТ!). Никто не видит главного - инженерия это подходы к решению задач в условиях ограничений. Вертикальное масштабирование? Оно не модно и не нужно. Слишком сложна-сложна-сложна. Проще навалить в горизонталь несколько сот серверов, обмазать очередями, гонять по сети гигабайты, вытереть ноги об Амдаля и Уэра. Архитектура? А куда это? Кроссплатформенность? Нинужна, есть докеры и куберы. Адище зависимостей? Упс, ну и что? Чотаковата? Все так делают. И менеджер рад, когда ИТ департамент шавермячной - две сотни человек на четыре сотни серверов в облаках. И инженерам без дипломов и малейшего представления, что такое инженерия, есть, чем заняться.

Чтобы хотя бы попытаться побороть эти две проблемы, Конана-Варвара и даже Геракла определенно будет маловато. Даже Илон слишком мелковат по своим масштабам и эксцентричности, чтобы поотрубать все головы этой Лернейской Гидре.

Рыночек порешал. Все побежали - и я побежал.

Именно поэтому - слова "техдолг", "рефакторинг", "оптимизация" (под которой понимают в самом лучшем случае поиск волшебного параметра, а в худшем - э, заменим HDD на SSD, подсыпем корочек процессорных, принесем чемодан плашек памяти - вот и вся оптимизация) в индустрии матерные.

А всё, что мы не будем рефакторить, убирая техдолг - автоматически идет в категорию "легаси". А мы не будем ничего рефакторить, изобретая тысячи уважительных причин, потому что не хотим (в первую очередь) и не умеем (во вторую).

Finally, ну и, как и всегда и со всех сторон - человеческая природа непобедима. Гром не грянет - мужик не перекрестится. До тех пор, пока не полыхнет - меняться ничего не будет, потому что незачем. Причина более, чем уважительная - "не сломалось - не чини".

А менеджмент и дальше будет языками зачёсывать, что-де, надо быть проактивными, решать проблемы прежде, чем они в полный рост встанут.

Нет, Гэри.

Пока метастазы не пойдут - никто и не почешется.
​​Документацию пишут только трусы

Благими намерениями, меж тем, устлана дорога известно куда.

Вроде бы, все всё понимают - за все хорошее и против всего плохого.

Но даже README за 20+ лет никто не удосужился начать прилично писать.

Один простой пример.

Практически ни в одном ридми к опенсурсным пакетам нет такой простой и очевидной вещи, как списка требуемых зависимостей - в самом начале, до того, как начинают идти команды configure && make && make install.

Соответственно, об отсутствующих зависимостях вы узнаёте лишь тогда, когда make начинает гавкать при сборке на отсутствие хидеров, библиотек, неполных описаний структур, отсутствующих или несовместимых функциях, и тому подобных радостях сисадминов. Что происходит почти всегда, если надо что-то нетривиальное собрать из исходников - просто потому, что мэнтайнеры не посчитали достойным включения в репо. Или включили, но собрали на своё усмотрение. Или собрали, но криво. Сто причин.

Знакомая ситуация, не так ли?

При этом всеми обгавканный autoconf вполне себе умеет в проверку наличия депендентов, хидеров, функций, а код можно писать с ветвлениями, позволяющими ему собираться на чем угодно и при почти любых установленных наборах зависимостей (привет докерофилам). Посмотрите, например, портабельный OpenSSH. Или любой другой пакет (да, их исчезающе мало), написанный командами натуралов. Без всяких докеров и куберов.

Однако не будем отвлекаться. Вернемся к минимальной документации, к ридми.

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

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

Если ты не можешь такую работу прилично делать от начала и до конца - возможно, за нее вообще не стоит браться.

Вчитываться в код и разбираться, почему работает так, а не иначе, почему не собирается на платформе XXXX - будет совсем не каждый.

Более того, это и нужно далеко не каждому.

Следовательно, даже если это противоречит концепциям айджайла-скрама, даже если документацию никто, в конечном итоге, не откроет - ее надо писать.

Завязаны шнурки или нет.

PS. Согласно так же всеми оплёванной концепции водопада, "кодирование, тестирование и документирование выполняется одновременно". Отцы-основатели тоже понимали, что программисты документировать не любят и надо, по-возможности, поймать их на берегу и заставить-таки писать.
​​Latency vs throughput, часть 1

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

За latency принято считать скорость отклика от поступления запроса до начала ответа сервера. В терминологии реляционных баз данных, это хинт оптимизации FIRST_ROWS. В базах данных, оптимизатор выбирает алгоритмы выполнения таким образом, чтобы строки как можно скорее начали выбираться и поступать клиенту. Обычно это алгоритмы типа nested loops, с квадратичной сложностью. Узким местом является сам алгоритм и его производительность, которая и ограничивает темп прокачки данных и, в целом, время выполнения запроса.

Througput, в противовес latency, ограничен лишь пропускной способностью железа. В реляционных БД это хинт ALL_ROWS, нацеленный на общее время выполнения запроса, а не на скорость его отклика и зависящий, в основном, от пропускной способности железа по IO. Оптимизатор выбирает алгоритмы типа hash join, имеющие сложность O(n log n) и им подобные. Узким местом является потребляемая память и пропускная способность железа.

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

По этой причине, многие программные решения имеют взаимоисключающий параметр (как logbias в ZFS или хинты глобальных целей оптимизатора СУБД Oracle), когда приходится делать выбор - что важнее, latency или throughput.

Если говорить об аллокациях памяти, то, в силу специфики алгоритмов, аллокаторы влияют в наибольшей степени именно на latency. В общем случае, именно там в процессе выполнения запроса, от начала и до конца, выполняется масса аллокаций памяти. Для алгоритмов throughput, память, как правило, выделяется в виде shared (в СУБД) или private buffers один раз и переиспользуется до конца запроса.

Именно поэтому аллокаторы практически никак не влияют на IO bound операции.

Следует иметь в виду также вот что. Микросервисные системы - на 3/4 являются IO bound. Сетевые операции - это IO. С одной стороны, latency для них как бы важна. С другой - IO всегда упирается в hardware throughput. С точки зрения практики, это означает, что микросервисы всегда лимитированы сетью и почти не поддаются нормальным методам оптимизации. Причем даже в тех случаях, если коммуникации осуществляются по loopback, никаких чудес не происходит и это по-прежнему сетевой IO.

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

Производители железа пьют до синевы в своих офисах за здоровье девопсов, соорудивших этот портал в Ад.

Справедливости ради, стоит отметить, что сетевой IO и раньше был проблемой, во времена монолитов и старфайров. Это, в частности, одна из причин, почему распределенные системы не получили широкого распространения - из-за непредсказуемой латентности. В силу чего для IO вообще и сетевого IO в частности резервировались отдельные процессоры, занимавшиеся только и исключительно вводом-выводом. И было такое, почти забытое ныне понятие, как "сбалансированная конфигурация".

В этой связи, было очевидно плохой идеей создание архитектур и подходов, многократно наращивающих этот самый ввод-вывод.

Как и было неоднократно сказано ранее - архитектурные ошибки обходятся максимально дорого и их практически невозможно исправить.
​​Latency vs throughput, часть 2

Означает ли вышесказанное, что тюнить бессмысленно, в том числе микросервисы?

Нет, не означает.

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

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

В самых лучших традициях Джеймса Уатта с его 1001 долларом и кувалдой.

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

Первое и самое главное - в большинстве случаев совершается три огромные ошибки:

1. Тюнинг фрагментарен. А именно, и его первая часть - сбор и анализ статистики, и вторая - собственно оптимизация - предельно точечны и, либо покрывают лишь небольшие фрагменты системы либо оставляют достаточно белых пятен по принципу "База Постгрес тормозить не может, аминь!"

Если вы не наблюдаете (тот самый термин "observability") систему сколько-нибудь полно, вы не можете ее поведение анализировать и, следовательно, эту систему тюнить.

2. Тюнинг хаотичен. Те, кто его выполняет, не руководствуются какой бы то ни было системой (пусть даже самой плохонькой) или методом и просто хаотично кидаются на видимые ботлнеки (даже на секунду не задумываясь о законе ограничений Голдрата) и пытаются их устранить, в надежде в одной итерации весь тюнинг и закончить - что является еще одной серьезной ошибкой. Крутить одновременно несколько ручек - плохая стратегия, надеяться одним ударом достичь всех целей тюнинга - еще более плохая стратегия. Такие подходы обычно заканчиваются либо тем, что оптимизация упирается в необходимость точечных или глобальных изменений архитектуры или кода; либо тем, что тюнинг прерывается в соответствии с поговоркой "Сделай плохо а потом верни, как было".

Однако существующие методики тюнингов транзактабельны - то есть, вы либо доводите тюнинг до конца, либо целиком откатываете и больше за него не беретесь. По крайней мере, в текущем виде.

3. Тюнинг реактивен. В точку - о нем задумываются, когда уже слишком поздно. В этом случае не получится ни дешево, ни безболезненно - будет страшно дорого и ужассссно больно. Как запущенный случай парадонтоза в стоматологии - только удалять и протезировать 18 зубов. И потом все равно придется перестроенную до основания систему тюнить. Снова. Чтобы избежать повторения ситуации.

Может показаться, что процесс оптимизации абсолютно контринтуитивен. Это, вообще говоря, не вполне верно.

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

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

Помноженное на кучу микросервисов, все это приводит к идее, что реактивный тюнинг, в случае распределенных архитектур, контрпродуктивен. И необходимо последовательно, методично и обдуманно, шаг за шагом выполнять необходимые шаги, придерживаясь хоть каких - но методов - для всей системы в целом.
​​Latency vs throughput, часть 3

Вернемся к тому, с чего начали. К latency.

Что представляет сегодня огромная часть систем? Массу распределенных мелких кусочков, обменивающихся - чем? - правильно, текстом. JSON over HTTP(S) по сети.

Может показаться - ачотаковата? Все так делают.

Увы.

Оставив в стороне текст, посмотрим глубже. Что есть обработка текстов?

Правильно - это строки. Строки - это динамические контейнеры. На куче. В современном мире - крупные контейнеры. Зачастую, 64к или даже более.

Миллионы аллокаций в секунду (как правило) при копированиях, парсинге, поисках-заменах (посмотрите, сколько реаллокаций может сделать replace() из string).

SGI Ropes не просто так придумали и собственные реализации-обертки поверх C-strings тоже пишутся не просто так. Кто слышал о Ropes? Поднимите руки. Никто? Как же так, Карл? Может, на SO натыкались на упоминание? Оно там одно. Нет, и там не видели?

Самым правильным, конечно же, было бы глобально минифицировать обмены (и киты это очень часто и делают - могут себе позволить) использованием бинарного протокола, верно? И сеть меньше грузится, и с байтами оперировать проще, чем со строками по 64к. protobuf - не просто так Гугл выдумал. Казалось бы, вот то здравое зерно, которое реально стоило бы позаимствовать у китов.

Взамен того, что маленькой такой компании - не Локхид-Мартину, не имеющей масштабов Гугла, не нужно абсолютно.

Однако же, нет. Мы будем гонять тексты терабайтами по сети и обрабатывать их построчно (вы же не знаете иного способа обработки текстов, верно? И никто не знает). Почему? "Потому, что можем".

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

Строки. Плюс оверкоммит.

Вот и вся лейтенси. Она становится непредсказуемой.

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

И, зачастую, кроме попыток распараллеливания (здравствуй, NVMe), радикально ее улучшить сложно или очень дорого (здравствуй, 10 Gb Ethernet и 40 Gb Infiniband - упс, простите, на инфинибэнд не хватило, вот вам Ethernet 40 Gb и даже о джамбо-фреймах слышал далеко не каждый девопс. FC-AL? Да мы и не слышали никогда о таком, в нашей малюсенькой такой компании на 200+ сотрудников, верно?).

На таком фоне, если оглянуться вокруг, недостатки монолитов как-то меркнут.

Однако, факт в том, что необходимости оптимизации все вышесказанное не отменяет.

И - да, подобно безопасности, оптимизация производительности не продукт, а процесс.

Зачастую, принципиально противоречащий краеугольной концепции TTM.

Альтернатива, однако же, много хуже. Наращивать вычислительные мощи до бесконечности не получится.
TheCacheWorks pinned «​​Рак разъедающий Легаси - это то, что мы не понимаем, понимать и поддерживать не хотим и вообще это NIH. А рефакторинг не нужен, родной! В действительности, вы не туда смотрите. Фундамент метастазирования индустрии мало того, что заложен не вчера - он практически…»
​​Пять, шесть - Фредди всех вас хочет съесть

С нумерацией версий в индустрии уже много лет творится какое-то безумие.

Общепринятая схема всегда была следующей:

Мажорная версия - минорная версия - патч - фикс.

X.X.X.X. Плюс-минус.

И всегда было по номеру версии более-менее ясно, чего ожидать от обновления.

NB. Изменение мажорной версии на единицу означало просто тектонические изменения. Во всяком случае, должно означать.

Что мы имеем сейчас? Маркетологическую чушь. "Мама, я добавил темную тему! Увеличим мажорную версию, ура!"

"Мы добавили новую свистоперделку сбоку - мажорная версия увеличивается, гип-гип!"

Нумерология - наука о чиселках.

Доходит до абсурда.

Такая нечеловечески сложная вещь, как Оракл - десятки миллионов строк кода - щелкает вообще мажорные версии через две или три. Что? Это должно означать, что бОльшая часть кода переписана? Как бы не так. Это просто маркетинг и ничего более - "Мы прикрутили AI - неважно, куда - плюс три мажорных версии".

Вероятно, Ларри на старости лет выжил из ума.

Файрфокс 126 версии. Ребята, у вас как с кукушечкой, вообще? Все хорошо? Что, вы кардинально перелопатили весь код снизу доверху? Или хотя бы переписали его бОльшую часть? Серьезно поменяли алгоритмы? Что?

LibreOffice. Была нормальная схема нумерации. Бац - и в 2024м 7.6.X внезапно делает скачок на 17 мажорных версий. Семнадцать, Карл. Это должно означать 17 крупных переделок? Нет, ничего подобного. Мы просто теперь каждый год будем увеличивать мажорный номер, по номеру года. Ненуачо? Все же так делают. Нормально же. Стильно - модно - молодежно.

Заповедники, где осталась прежняя, сколько-нибудь логичная, схема нумерации - можно по пальцам одной руки пересчитать. Буквально.

А вы что хотите делайте. Хотите - ченджлоги читайте. Хотите - верьте им.

Блажен, кто верует.
​​Необходимо и достаточно

Очередное подтверждение тезиса, что проблемы Гугла - не ваши проблемы.

Более того.

Если вы, не будучи Гуглом, сталкиваетесь с подобными проблемами - вы что-то делаете очень неправильно.

Масштабы и задачи должны соответствовать.

Есть два таких понятия в математике - "необходимо и достаточно".

"Для решения данной задачи необходимо и достаточно...." - примерно так.

Это и есть инженерный подход.

Не "как-нибудь решить, все равно как и все равно, почём".

А "решить оптимальным образом, необходимо и достаточно".

Дошло же до бизнеса, причем молниеносно, что использование халявного СПО - трындец, как выгодно. Даже с учетом, an mass, сомнительного качества этого самого СПО.

Точно так же до бизнеса должно дотумкать, что использование методов Гугла не в Гугле не Гуглу не по средствам и можно (и нужно) хорошечно сэкономить. Почти как на халяве.

Гугл решает свои задачи своим образом - может себе позволить.

Но, как уже неоднократно говорилось - вы-то не Гугл. И крайне маловероятно, что когда-нибудь станете Гуглом.

Поэтому, по сути, вам не нужны ни архитектурные, ни технологические, ни даже программные методы Гугла.

Молодчиков, никогда в жизни не работавших нигде, кроме, от силы, региональных "Рога-и-Копыта, Inc.", но браво пишущих в резюме "Докер, кубер, микросервисы" надо гнать в три шеи на этапе первичного отсева. Resume Driven Development не нужен тем, кто не является FAANGом.

Когда - и если - местечковые "Рога-и-Копыта, Inc." вдруг хотя бы на парсек приблизятся к масштабам FAANG - тогда и только тогда мы поговорим о масштабировании по методам FAANG, наворачивании многослойных абстракций, разработке по методам FAANG и тому подобном.

Ц - целесообразность.

А до тех пор сидим в своем пыльном углу ровно, не отсвечиваем, мирно пилим классику. И делаем это качественно.
​​И этот крокодил тоже Локи - часть 1

Учение Амдаля-Уэра непобедимо, потому, что оно верно.

С момента появления многоядерных процессоров (более 15 лет назад), а на самом деле гораздо раньше, со времен SMP систем, гигантской проблемой являются блокировки. На самом деле, проблема еще старше и существует со времен многопользовательских систем, однако в полный рост она встала с приходом многоядерников.

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

После появления многоядерных процессоров в попытках как-то объегорить закон Мура весьма быстро выяснилось, что многоядерники с традиционным софтом вчистую продувают архитектурам SMP с одноядерными процессорами. Не сразу, однако начало доходить, что треды - это не столько о производительности, сколько о масштабировании. И сразу же обнаружился тупик, связанный с блокированием и архитектурами.

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

Индустрия пошла по двум легким путям.

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

Второй - а давайте сымитируем MPP там, где MPP нет и в помине. Запустим на процессоре кучу формально независимых друг от друга виртуалок, пусть они на ядрах крутятся. Нет, там нет MPP - у вас сторидж общий. Нет, гиперконвергенция это не про MPP. И, тем более, это не кластер, кластер это о другом совсем и устроен совсем не так.

Как итог, первый путь привел к тому, что потенциально выгодное развитие SMP в сторону вертикального масштабирования одиночного сервера уткнулось в закон Амдаля-Уэра и привело к порочному горизонтальному масштабированию в тысячи серверов с количечством ядер, не превышающим 4-8 (тот самый порог Амдаля).

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

Оба пути ведут непосредственно в ад, но этим никто особо не заморачивается, пока существует какой-никакой, но рост.

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

Проблема существует почти во всем коммерческом и свободном ПО. Как и было сказано, ее предпочитают не замечать.
​​И этот крокодил тоже Локи - часть 2

Дьявол, как обычно, в деталях.

Без кардинальной переработки ПО проблема блокирования неразрешима. Да, можно ограничиться серверами-маломерками, не более 4х ядер, либо нарезать огромную суповую тарелку на виртуальные машины с ограниченным количеством ядер на машину (что имеет свои проблемы на уровне гипервизора и работы системы в целом, но эти проблемы тоже предпочитают не замечать).

Более того, архитектура thread-safe с ростом числа ядер (тредов) неизбежно упрётся все в того же Амдаля и Уэра. Следовательно, наращивание числа ядер на кристалл имеет весьма мало смысла для типовых задач, так как требует очень специфического программирования.

Итак, очевидно, что короткого и легкого пути не существует. Что насчет других подходов?

Их не очень много.

Семафоры вместо мьютексов (а, точнее, массивы семафоров на общие области памяти с множественным доступом) имеют смысл, однако индустрия предпочитает в этом месте один мьютекс (читай - giant lock) и пусть весь мир подождет. Привет, Оракл. Здравствуй, Бангалор.

Lock-free алгоритмы (включая атомарные операции) таковыми лишь называются. Помимо дьявольской сложности разработки, атомарные операции не являются неблокирующими в полном смысле этого слова. Большинство атомарных операций блокируют шину процессора (пресловутый префикс LOCK, который ставится автоматически - смотрите ассемблер, интрузивные операции всегда блокирующие - в случае сколько-нибудь жестких барьеров памяти), таким образом ждет уже не софт, а хард. Более того, работа с атомарными типами может быть более медленной, чем с обычными данными и, на закуску - совсем не всегда атомарные типы являются под капотом истинными lock-free (да, они могут использовать высокоуровневые мьютексы а не атомарный блокирующий ассемблер). Стоить переборщить с атомиками - и здравствуйте, хардверные ожидания освобождения шины. Нет, число ножек процессора конечно, на каждое ядро свою шину почему-то не сделали. Есть, конечно, каналы, есть NVM. Но сравним число ядер с числом каналов. Добавим кэш третьего уровня, чтобы купировать. Может, четвертый уровень еще? Ну и IO обмажем терабайтными кэшами, чтобы два раза не вставать. То есть, очевидно, что атомарность - не решение.

Подход thread-aware, он же lockless, заключается в снижении уровня блокирования насколько это возможно, в том числе по времени выполнения кода. Заключается он в копировании части общих данных под блоком в TLS (thread-local storage) и последующей работой тредов с локальными копиями без блокирования. Таким образом драматически снижается время удержания блокировок и значительно повышается способность программы к вертикальному масштабированию.

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

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

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

Performance Team в компании-разработчике ПО - это не роскошь, а насущная необходимость. Столь же серьезная, как и существование департаментов QA.
​​И этот крокодил тоже Локи - часть 3

В финале познакомимся с самым главным Черным Чёртом, который в деталях.

Увидеть Амдаля и Уэра (lock contention) в Линуксе вы влегкую не можете.

Нет в ней в шаговой доступности функциональности lockstat, которая в любой момент под рукой и не просаживает производительность системы, которую нужно мониторить, как в Солярис.

Чтобы увидеть блокировки (и предпринять какие-то действия или хотя бы понять эффект), вам надо для начала собрать ядро с соответствующей опцией.

Из исходников. Задача для гентушника на пару выходных.

И, вдогонку, получить дополнительный немалый оверхед. Оно так написано. И задокументировано. В продуктивной системе это делать никто не станет, само собой разумеется (в Солярис это не так и там в любой момент можно посмотреть, кому стоим - кого ждем; но даже там некоторые админы никогда не слыхали о блокировках и их статистике. Однако для разработки эта система идеально подходит, будучи настоящим инструментальным ящиком).

DTrace и Systemtap? Это для слабаков.

Соответственно, локи это такой зверь, о котором некоторые как бы слышали, но увидеть его не могут - следовательно, он мифический.

Определить lock contention по косвенным признакам?

В добрый час, обычное ожидание блокировки не повышает утилизацию процессоров. Совсем. Вы видите некий hang, при околонулевой утилизации процессоров. Что происходит? Неизвестно. То, что нельзя увидеть - нельзя исправить или хотя бы понять.

Задумавшийся спинлок может увеличивать утилизацию, особенно при дэдлоке. А может и не увеличивать - смотря как он написан. Процесс, застрявший на мьютексе, после ожидания будет сброшен планировщиком в очередь и это тоже впрямую не увидеть. Что-то ждет. Что - непонятно.

Ну, а раз глаз не видит - желудок, соответственно, не страдает.

Проблемы не существует.

Плохие новости - статистика блокировок лишь обозначает проблему. Для ее понимания нужно очень хорошо знать архитектуру ОС и используемого ПО.

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

Разумеется, этого никто не делает и делать не станет. Глупости какие-то, выдумали что-то про блокировки.

Переписывать открытый халявный код? Вот еще, пусть миллион мух напрягается. Им за это даже платят. Может быть. Не мы - мы не платим и не собираемся. Нахаляву уксус сладкий.

Результат немного предсказуем.
​​Долгий джонт

За без малого полстолетия в примитивах синхронизации не придумано ничего лучше семафоров, мьютексов и спинлоков.

Первые два сравнительно просты.

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

Мьютексы плохи тем, что это всегда syscall (в конечном итоге), переключения контекста, режим ядра со всеми вытекающими. Очень большой оверхед, непредсказуемые ожидания при высокой утилизации. Гибридные реализации, разумеется, существуют (придуманы еще Sun Microsystems), когда сперва мы крутимся на спинлоке, после определенного числа оборотов уходим в мьютекс. Оракл позаимствовал идею для латчей СУБД и имел недокументированный параметр spin_count, позволяющий растянуть время пребывания в режиме спин. Это повышает оверхед, однако снижает лейтенси примитивов синхронизации при выходе из блокировки.

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

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

Если говорить о C++, начиная со стандарта C++20 (наконец-то) реализованы в относительно полном объеме примитивы atomic_flag, позволяющие легковесно реализовывать синхронизацию класса спинлок зачастую без спинлока как такового (с циклом). Оставим за кадром комитет и его техническую политику, а также разработчиков компиляторов.

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

Безусловным плюсом при этом является тот факт, что традиционные спинлоки, содержащие бэк-офф в той или иной реализации (и реализации PAUSE) не позволяют прервать ожидание при изменении состояния флага, совпавшего по времени с ожиданием. Прерывание ожидания технически можно реализовать на conditional variables, однако есть две проблемы - spurious wakeups, которые необходимо обрабатывать - и странные реализации CV в некоторых платформах (привет, FreeBSD).

Атомик флаги решают обе этих проблемы (с той или иной эффективностью на различных платформах), так как wait/notify как раз и позволяют реализовать этот самый переменный бэк-офф с возможностью его прерывания в момент изменения состояния флаговой переменной.

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

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

Второе - высокоскоростной софт зачастую испытывает скачки лейтенси уже от физической реализации DRAM. Те самые RAS/CAS. Эти флуктуации часто вызывают неописуемое изумление, но, увы - физика очень бессердечная штука. Ждем хардвера - означает, ждем хардвера.

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

Без которого сколько-нибудь эффективная реализация масштабируемого (в том числе по вертикали) ПО в принципе невозможна.
​​0,1,2,3,4 - запирайте дверь в квартире

Фредди Крюгер умер за наши грехи.

В пятницу, 13го, приходят плохие новости.

Один из самых опасных багов - Use-after-Free - заметается операционными системами под ковёр.

Все просто - после "освобождения" памяти вызовом free() указатель (обычно shared, и не всегда умный) остается валидным и память автоматически не освобождается.

Нет, Гэри. free() означает буквально следующее - "Сообщаем аллокатору, что эта память может быть освобождена". Программист сам, ручками, должен нуллифицировать указатель после освобождения. Фактическая очистка этой памяти, конечно, может быть выполнена. Вручную. Вызовом безопасного free (который, кстати говоря, стандартом POSIX не определен).

Суть проблемы, однако, в другом. Память кучи контролируется ядром и освобождаемые блоки (мы же помним, что ядра ОС у нас однопоточные и асинхронные? Ведь помним же?) немедленно не очищаются и в пул свободных мгновенно не попадают. Попадают они в очередь освобожденных блоков брокера памяти ядра.

А так как указатель до нуллификации остается валидным, данные в блоке доступны по указателю еще какое-то время.

Вы уже содрогнулись?

Мы содрогнулись, когда увидели, как программисты на Реддите на полном серьезе задавали вопрос - "А нужно ли нуллифицировать указатель после освобождения памяти?"

Держитесь за креслица крепче. Use-after-free крайне трудно обнаружить статическим анализом (анализаторы могут в упор ее не видеть, такие дела), а операционные системы - все без исключения - справедливо считают нуллификацию указателя прерогативой программиста, и все до единой (из общего назначения) имеют очереди брокера памяти ядра. Нет, Раст в такой ситуации не поможет. Анализ потока выполнения между модулями может быть крайне затруднен в случае матерой асинхронщины на коллбэках.

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

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

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

Вы имеете дело не с опасными ЯП - а, по сути, с опасными ОС. И незнание того, как они работают под капотом, недопустимо для программистов. Вне зависимости от используемых ЯП.
​​Ослик IO и все-все-все, часть 1

Извечная проблема индустрии с некоторых, весьма давних, пор - это непрекращающийся колоссальный IO. Для которого, в общем, существуют весьма близкие лимиты физической пропускной способности. И эти лимиты больно бьют по производительности систем, бюджетам IT-департаментов. А вот производители оборудования - в особенности, сетевых карт и SSD, наоборот, пьют в своих офисах до отвала печени за здоровье айтишников, которые прямо и непосредственно, собственными руками, способствуют повышению стоимости акций этих производителей до небес.

Страдают все - ДБА, системные администраторы, Гретхен.

И лишь вендоры и CTO радуются повышению капитализации и открытию новых датацентров.

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

Проблему IO для понимания следует разделить на две большие части.

Часть первая, архитектурная.

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

Помимо нагрузки на системы - под это задействуется до 60% мощностей - создается массированный оверхед на собственно приложения. Всем известно, что логирование - это строки в промышленных масштабах, что std::string - это контейнер с массой аллокаций, что поток строкового IO это огромная нагрузка буквально на все подсистемы, включая сетевые - и тем не менее.

Про kstat, который без всяких логов собирал и собирает массу статистики без всякого видимого оверхеда - нет, не слыхали.

То, что возможно, по меньшей мере, 40% логов не писать, и либо копить статистику в фоне, в быстродействующих структурах памяти, и извлекать по требованию и триггерам, почти без оверхеда и без потерь наблюдаемости, либо включать только на время отладки - нет, в головы не закрадывается. И противоречит современным методологиям - отладка по айджайлу идет непрерывно, весь жизненный цикл систем.

Часть вторая, алгоритмическая.

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

Чрезвычайно неэффективные запросы (привет, квадраты и кубы; здравствуй, ORM с его генерацией SELECT * FROM SELECT * FROM SELECT...) ко всему объему данных, причем неоднократно, в один поток (здравствуй, шардирование - а до него партишенинг; можно было хотя бы параллелить; ах, да - сториджи тоже должны параллельный аксесс аппаратно поддерживать, сюрприз).

Эффективный доступ к данным, элиминация квадратичных и кубических алгоритмов доступа, нет?

Нет, разумеется.

Ну а зачем, в самом-то деле? NVMe уже изобрели; что с того, что они как крыло от Боинга стоят?

Никого не волнует эффективность IO. Железо обязано аппаратно справляться с любой мыслимой нагрузкой, алгоритмика это для слабаков.

Оставив в покое алгоритмику - даже такая простейшая и примитивнейшая оптимизация уровня ОС как MAXPHYS это настоящий рокет сайенс и за все время существования использовалась лишь в единичных случаях особо продвинутыми системными администраторами.
​​Ослик IO и все-все-все, часть 2

Исторически с IO сложилась парадоксальная ситуация.

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

Администраторы проблему видят, но мало что могут и хотят сделать - нужно вникать, разбираться, проводить эксперименты - на живых системах, чего никто не позволит; вносить какие-то изменения - страшно; кроме того - "работает? не трогай!"

Когда в конечном итоге приходят проблемы - начинаются лихорадочные поиски волшебной пули.

Суть, однако, в том, что волшебной пули не существует.

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

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

Разумеется, никому такие страшно дорогие и очень болезненные операции не нужны.

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

Единственное решение, которое просматривается - с самого начала, от проектирования, иметь Performance Team, следующую мантрам Брендана:

1. Не делай этого.
2. Сделай это только один раз.
3. Делай меньше.
4. Делай позже.
5. Делай это, когда нет других дел.
6. Делай это параллельно.
7. Делай это оптимально.

К IO эти мантры относятся непосредственно.

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

Однако посейчас наблюдаются случаи (а на десктопах подобные конфигурации вообше норма), когда на 256 ядер в системе приходится лишь один-два SSD.

Старую добрую рекомендацию (для старых и слабеньких процессоров), что на 1 процессор (ядро) должно приходиться минимум пять шпинделей - сейчас уже никто и не помнит.

Резюмируя вышесказанное - проблему IO решить, или, как минимум, значительно ослабить, в общем-то, можно, однако подходить к решению следует системно, от начала проекта и до его завершения. Сама собой эта проблема не решится и хардверными средствами ее решить в полном объеме не получится.
​​Фантастические утечки и где они обитают

Поговорим об утечках памяти.

Утечки памяти делятся на две неравные категории - утечки истинные, подтверждаемые Valgrind и ему подобными инструментами, и утечки мнимые, в действительности утечками не являющиеся.

Первых примерно 40%, в максимуме. Вторых - 60%.

Что такое истинная утечка памяти?

Как всем известно, память кучи выделяется в пространстве памяти ядра ОС и частично контролируется ядром.

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

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

В некоторых случаях такая потерянная память может быть освобождена сборщиком мусора, если он существует в рантайме или аллокаторе. Мы говорим "в некоторых случаях", потому что алгоритмика сборщиков очень разная и не всегда заточена подбирать неконтролируемый мусор - все это требует определенного, весьма немалого, оверхеда и дополнительных движений в процессе выполнения кода.

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

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

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

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

Мнимые утечки сборщики мусора устранить не в состоянии по определению. Эта память продолжает оставаться контролируемой, и этот контроль целиком в руках разработчика.

По опыту, на мониторинге истинные утечки обычно выглядят как постоянный небольшой градиент прироста утилизации памяти, с самого начала работы приложения. Медленный и печальный. Утекать может не только память, но и ресурсы, с ней связанные, например, файловые дескрипторы.

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

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

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

Единственная возможность снизить вероятность появления истинных утечек - это тщательное написание кода и тщательное же его тестирование.
​​Почему не прижился memory capping?

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

Когда в 2005 году была предложена (и реализована) идея memory capping, казалось, что вот она, волшебная пуля - разом позволяющая и решить проблемы утечек, и, вместе с остальными ресурсами, ограничить аппетиты систем и приложений по оперативной памяти.

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

Как работает memory capping?

Для приложения (или профиля выполнения или ресурсной группы) задается некое число мегабайт, отслеживаемое операционной системой по заданным условиям. Как только эта величина достигается - операционная система на запросы malloc() от приложения начинает выдавать - нет, не JSON с кодом 403 DENY - а NULL. Это ожидаемое поведение, согласно стандарту.

Что означает для приложения NULL, полученный от аллокатора вместо указателя на выделенную память?

Он означает Out of memory.

Как приложение отреагирует? Как вообще написаны аллокации в приложениях?

Правильно - они написаны очень примитивно: после аллокации приложение - в самом лучшем случае - проверит возвращенный указатель на NULL.

Весьма часто - не проверит. В самом деле, кого это интересует, все равно сделать ничего нельзя, а приложение так и так упадет, fatal.

Если память не может быть выделена - может приложение продолжать работу?

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

Результат немного предсказуем - никто не собирается переписывать ни подсистему памяти ОС с учетом возможности работы какого угодно ресурс менеджмента, ни приложения - от которых ожидается сколько-нибудь корректное поведение с учетом возможности отлупа от менеджера ресурсов.

По этой причине сейчас нигде, ни в каком виде, в ресурс менеджерах ОС невозможно найти memory capping.

В своем непосредственном виде он остался лишь в практически не имеющих применения внутренних менеджерах ресурсов некоторых СУБД.

Вот такой, один из многих, пример, как блестящая с виду идея, не находит, ввиду полной непродуманности, абсолютно никакого вменяемого применения в индустрии.

И единственная, по сути, возможность как-то контролировать расход RAM - это тщательно программировать приложения и настраивать ОС.
​​Тормозяки

Существует один малоизвестный фактор производительности, внезапно и заметно на нее влияющий.

Этот фактор - страничный (файловый) кэш операционных систем.

По умолчанию (а, зачастую, включенный параметром в предположении определенных сценариев работы ОС) он постепенно занимает всю свободную (не занятую ядром и кучей, которая тоже контролируется ядром) память.

Файловые системы используют его для кэширования внешних носителей.

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

Так вот, "незамедлительно" это не совсем верно.

Незамедлительно освобождаются лишь те страницы, которые только читались.

Для грязных блоков должен сперва произойти чекпойнт - то есть запись их на файловую систему. А если эти блоки принадлежат активным в данный момент процессам, то и анонимные страницы этих самых процессов должны быть записаны в своп (у вас же есть своп достаточного размера? Ведь есть? Ведь правда?)

Такой, весьма небыстрый - даже для SSD - процесс может быть спровоцирован одной-единственной аллокацией одной-единственной страницы размером 4 кб.

На которой может произойти резкий и внезапный скачок дисковой активности с резким же снижением производительности.

Известно ли об этом разработчикам ОС?

Да, известно.

ZFS, которая славится способностью занимать очень много памяти под кэш, в реализациях натуралов имеет хард лимит кэша ARC (что в принципе мало кому известно), zfs:zfs_arc_max. Данная настройка способна сломать устоявшийся стереотип, что "ZFS сколько памяти находит - столько и занимает". Нет, не занимает. Мы запрещаем пересекать хард лимит. Он потому и хард, что его невозможно перепрыгнуть (хотя были случаи, связанные с критическими багами). Разумеется, нужно устанавливать его в адекватный размер (обычно 1/4 всей RAM).

Всеми нежно любимый за халявность Линукс такого хард лимита на размеры файлового кэша не имеет. Что иногда (внезапно и непредсказуемо) приводит к подобным неожиданным эффектам, как возникающие как бы из ниоткуда тормоза.

В сочетании с тенденцией с установкой огромного количества плашек в сервера и непобедимой тягой девопсов к отключениям свопа это прямо приводит к проблемам, даже на SSD.

Единственная, по сути, возможность - если не избежать, то хотя бы немного сгладить эту проблему - это создавать разделы (не файлы!) подкачки адекватного размера и - для Линукс - воспользоваться параметром vm.min_free_kbytes, задав его пропорционально размеру установленной физической памяти (обычно в размере 5-10% от объема RAM).

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

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

Обратите внимание, что рассмотренный фактор способен повлиять как на поведение продуктивных систем, так и на различные бенчмарки (вызывая необъяснимые скачкообразные изменения результатов, о чем будет отдельный разговор).

Резюмируя вышесказанное, оптимизация производительности это кропотливая работа с полным пониманием того, что происходит под капотом в системах. Волшебной пули не существует.
​​Месть Бешеных Чиполлино, часть 1

В чем подвох бенчмарков и почему вменяемые инженеры их глубоко презирают?

Подвохов на самом деле много.

1. Бенчмарки, несмотря на все потуги быть независимыми и тестировать всё и объективно, бывают такими крайне редко. Мало кто из вендоров заинтересован выпячивать свои слабые стороны и топить сильные, поэтому вендорским бенчмаркам веры быть вообще не должно. В принципе. Следует вспомнить историю, на основании которой можно утверждать, что коммерческий успех сверхгиганта nVidia состоялся, почти главным образом благодаря ее инвестициям в разработчиков компании Mad Onion, написавшим необычайно привлекательный 3D бенчмарк, с которого и начался по-настоящему значимый взлет продаж видеокарт. Этот случай нельзя назвать коммерческим подкупом в судебном смысле, однако практическая разница между бенчмарком и последующими попытками выполнения реальных игр на рекламируемых видеокартах (в частности, пресловутого Max Pain, движок которого был использован в бенчмарке) впоследствии не афишировалась. Уплочено. Бенчмарки это коммерческий инструмент и не более. Точка.

2. Если мы говорим не о консьюмерских бенчмарках, а об использовании их в индустрии, следует иметь в виду такой самоочевидный (не для всех, к сожалению) фактор, как статистическая значимость. Чтобы избежать эффекта Майкельсона-Морли (когда один-единственный отрицательный результат был принят как окончательный, что является потрясающим факапом современной физики, по мнению инженеров), следует выполнять множество прогонов и, как минимум, усреднять результаты, а также приводить их хотя бы к 90му перцентилю. Причина? Она очевидна - компьютерные системы сложны и склонны к нелинейному поведению по самым различным причинам. При неоднократных прогонах часто обнаруживается значительный разброс результатов, причины которого почти никогда никто не выясняет (о чем чуть ниже).

3. Правило, которое игнорируется повсеместно - для хоть сколько-нибудь объективной картины бенчмарки (как и индустриальные тесты) следует проводить под полным мониторингом. Иначе говоря, надо, как минимум, включать Заббикс на тестируемых системах. В идеале, надо непрерывно снимать исчерпывающую системную статистику по большинству показателей, зачастую добавляя туда значимые для потребителя метрики - такие, например, как фрагментация памяти и т.п. В подавляющем большинстве случаев, однако, это требование не выполняется и в принятии решений полагаются исключительно на показатели самого бенчмарка. Этим часто грешат даже вендоры (имеющие коммерческий интерес впарить свою продукцию, о чем не следует забывать).

4. Референсные бенчмарки (от вендоров и их аффилиатов) проводятся на специально тюнингованных специалистами экстра-класса системах, что весьма далеко от реальных систем потребителей и заказчиков (как, например, всем известные тесты TPC или SPEC). Причина проста - якобы некоммерческие организации тоже любят деньги и вендоры, разумеется, не будут любить плохие результаты. Говоря начистоту, мало кто из вендоров (если только не имеет реального подавляющего преимущества; и даже в этом случае) не захочет показать товар лицом и продать свои решения. Разумеется, они будут давать рекомендации, доступ к внутренней документации, даже своих специалистов. Вкладывать деньги в результаты - прямо или косвенно. Это бизнес, ничего личного.