MCP

вторник, 9 апреля 2019 г.

Странная реализация HttpClient в .NET Core

Представьте что у вас есть приложение. Не очень маленькое, но и не гигантское, работает помаленьку развивается, и тут вы совершенно случайно замечаете в мониторинге, что один из сервисов жрёт 25% CPU. Хм... вроде многовато, но мало ли что. Может боты набежали на сайт и смотрят все странички, или пользователи ходят. Да и 25% немного, запас огромный (400% максимум). Но на всякий случай сервис перепускается и загрузка падает до 1%. Вы думаете — какой-то глюк, но через некоторое время загрузка процессора растёт опять.

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

Когда я в очередной раз сдался, я не выдержал, и подцепился дебаггером к боевому серверу напрямую. И... ничего не понял, всё выглядит нормально. Отцепился — загрузка 0%. Тут у меня уже задёргался глаз. Через некоторое время повторил — да, подключение дебаггера явно лечит проблему, как и перезапуск приложения. Но это ведь не выход и не решение. А вдруг это какая-то проблема у нас, и если в этом приложении данную проблему можно замаскировать, то она может выстрелить где-то ещё. Так что пришлось разбираться дальше.

Неожиданно удалось нагуглить данный тред на GitHub'е. Проблема у Microsoft'овской библиотеки и связана она с HttpClient. Утверждалось, что нельзя его создавать на каждый HTTP запрос, а надо использовать статичный. Правда в документации есть пример использования:

В котором его заворачивают в using. Я попробовал покопать в данном направлении и выяснил первую забавную вещь: использование статичного HttpClient в .NET Core быстрее раз в 5, чем создавать его на каждый запрос. Но тут нюансы с самим временем запроса, но смысл в том, что уже есть причина использовать статичную версию. При этом под "большой" .NET разницы нет никакой.

В общем, выложил новую версию приложения, но проблема осталась. (может я не туда копаю?). Попробовал переключиться с .NET Core 2.1 на 2.0 — проблема ушла. Т.е. проблема есть только под core, под Linux и версия должна быть больше чем 2.0 (более свежие не проверял). Читаем документацию и видим, как разработчики Microsoft, светясь от гордости, рекламируют новую реализацию Http Client Factory, через которую и идут все запросы (не может Microsoft просто написать библиотеку, делающие HTTP запросы, внутри обязана быть магия!). В общем теперь всё managed, в 10 раз лучше и в 50 раз быстрее. Ага, и сделано как раз в 2.1

Прикидываю, где ещё скрылись HttpClient'ы и нахожу библиотечку для отправки Push-уведомлений, которая внутри себя создаёт данный класс. Собственно, поэтому в первый раз и не было найдено это использование. Переделываю использование данной библиотеки и проблема исчезает! Всё работает замечательно.

Тут я начинаю громко материться, но нахожу в документации небольшую приписку:
Т.е. они знают о данной проблеме! Но явно про это написать было стыдно, так что засунули всё в сноску, зато не будет никаких претензий от разработчиков. Сноска есть? Есть! Значит читайте внимательнее.

Получается совершенно странная ситуация: Disposable-класс, который надо использовать синглтоном. При том, что все предыдущие реинкарнации подобных классов были честными Disposable-объектами. Но тут решили сделать по-другому. Зачем? Я пока не понял. Но свинью подложили знатную. Представьте, у вас есть базовая библиотека, написанная под большой .NET, вы её мигрируете на Core, пользуетесь под Windows, всё нормально, а потом, спустя длительное время замечаете, что приложение жрёт много процессора. Вам нужно будет пройти квест, подобный моему, чтобы найти проблему, переписать библиотеку и весь остальной код, обновить всё что нужно и всё из-за странного архитектурного решения разработчиков из Microsoft.

воскресенье, 10 февраля 2019 г.

Деградация Windows Server

Есть у меня простенький компьютер. Был куплен в порыве шопоголизма на распродаже. Я так и не придумал, чем занять данный компьютер, поэтому накатил на него Windows Server 2016 (По факту LTSB сборка 10-ой винды 1607, которая Anniversary Update, но с серверными фичами). Компьютер после запуска (и ожидания 8 минут, пока винда разберётся со своими внутренними делами) выглядел так:

Если не вдаваться в подробности, то запущено 54 процесса, 848 потоков и занято 0.9Гб оперативки. Вполне допустимая ситуация для пустого сервера, хотя всегда хочется меньше.

Но я решил потестировать недавно вышедший Windows Server 2019, эта та же Windows 10, только уже злополучная 1809, October 2018 Update. Я просто обновил сервер, подождал джентльменские 8 минут и результат на экране:

Процессов уже стало 110 (ровно в два раза больше!), количество потоков увеличилось всего на 300 штук, что уже лучше, хендлов стало больше почти в 2 раза и сожрано стало на 400МБ оперативной памяти больше.

Ещё раз, для понимания бреда. За 2 года разработки операционная система на свои личные нужды стала использовать гораздо больше ресурсов. Может от этого она стала быстрее или лучше работать? Как-то незаметно. Вместо оптимизации системы в итоге получаешь просто ещё больше внутреннего потребления. Зачем? А просто так, потому что программисты Microsoft могут тратить ресурсы как захотят. Железо же становится быстрее, памяти больше, никто и не заметит. И вот это бесит неимоверно.

UPD: Оказывается, это сознательное решение Microsoft, они в 1703 разгруппировали сервисы, если в системе больше чем 3.5ГБ памяти. Объяснили как всегда надёжностью и безопасностью. Но памяти это, конечно же, жрёт больше, о чём не скрывают. А то, что "стабильность" теперь зависит от количества оперативной памяти — выглядит это очень странно.

UPD2: Поиск решения проблемы привёл на следующую ссылку, надо в реестре по пути:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control отредактировать (создать?) ключ SvcHostSplitThresholdInKB, изменив там количество памяти для разделения процессов. Итог изменения ключа следующий:

Процессов и памяти резко уменьшилось. До старых значений ещё не доходит, но всё-таки результат стал получше.