Как стать автором
Обновить

Комментарии 15

Уважаемый автор, нижайшепрошу прощения, но мне кажется, что вы проиграли пошли не туда ещё на этапе "Подготовка приложения".

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

Видите ли, ее можно решить написав небольшую обёртку с DI (мы же хотим красиво), поставив лок против race condition, и все красиво запаковать в минимальное количество абстракций.

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

Наверняка можно и так, но проблему долгого запуска при холодном старте это всё равно не решит.
А так - да, тоже одно из решений. В целом я уверен, что есть и другие решения, которые можно применить. Я описал одно из них. В чём проигрыш - не понял, ну ладно, Вам виднее.
По поводу "мы же хотим красиво" - не знаю, как по мне так было бы красиво, если бы Яндекс Функции позволяли не писать такой костыль, как Вы предложили, а поддерживали бы stateless из коробки.

Решить конечно можно, но чтобы решить надо знать, в то время как ASP .NET нас приучил, что оно stateless из коробки и каждый запрос выполняется в своем изолированном scope и общие моменты надо явно проговаривать как singleton, а тут получается наоборот и это контринтуитивно.

Всё именно так, и потому и приходится изобретать всякое)

Признаться, яннп, как должно выглядеть ваше решение и причём тут race condition. Но если вы пришлёте работающий Hello, world — это будет действительно полезный комментарий. Уверен, многие скажут за него спасибо. Да и я тогда в статью ваше решение добавлю. С указанием авторства, разумеется. Тем более раз обёртка небольшая - не должно занять много времени

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

Вот микропример того, что я имел ввиду: https://pastebin.com/bG6DyJxn

Что такой подход даёт:

  1. App.Run можно вызывать из любых потоков, в т.ч. из нескольких одновременно.

  2. В BuildContainer инициализируете ваш DI, этот код будет запущен лишь один раз на весь хостовый процесс/аппдомен.

  3. При любом вызове вы получаете ссылку на IServiceProvider, который находится в скоупе, который будет успешно закрыт после выполнения вышего делегата.

Как использовать: рисуете свой хендлер, внутри оборачиваете свой бизнес-код в делегат:

public class Handler
{
public string FunctionHandler(int arg)
{
return App.Run(provider =>
{
// Здесь бизнес-код, все сервисы доступны через provider.
return $"Hello world_{arg}";
});
}
}

Приветствую! Спасибо за ответ, идею понял.

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

В любом случае я протестировал ваше решение в Яндекс Функциях. Я добавил весь пример из https://pastebin.com/bG6DyJxn, а ещё добавил класс:

internal class SomeClassFromLibrary
{
    internal static int Counter { get; set; }
}

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

namespace StatelessTest;

public class Handler
{
    public string FunctionHandler(int arg)
    {
        return App.Run(provider =>
        {
            // Здесь бизнес-код, все сервисы доступны через provider.
            SomeClassFromLibrary.Counter += arg;

            return $"Counter: {SomeClassFromLibrary.Counter}";
        });
    }
}

И ожидаемо получил тот же результат, что и при использовании встроенного .NET-окружения Яндекс Функций. При частых обращениях, когда вызовы обрабатывает подготовленный экземпляр, значение Counter будет накапливаться, т.е. stateless не гарантируется.

Тестирование приложения
Тестирование приложения

Более того, среднее время работы функции при холодном старте увеличилось до 317 ms против 216 ms при использовании встроенного .NET-окружения. И это мы ещё не зарегистрировали и не использовали ни одного сервиса, просто добавили для них обёртку.

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

Но я очень благодарен за проявляемый интерес, если будут ещё варианты решения описанных проблем - готов и их протестировать и сравнить с имеющимися решениями!

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

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

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

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

Не понял. Если у вас долгоживущее приложение (из-за частых запросов), то регистрация сервисов (=стартап) как раз будет происходить лишь единожды.

Боюсь вас снова разочаровать, но вы неправильно протестировали.

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

Моё решение не модифицирует магически поведение статичных классов.

А какую проблему тогда оно призвано решить? Я не совсем понимаю. В статье описана конкретная проблема - отсутствие штатного stateless в реализации облачных функций. Через добавление статического класса я проверяю, сохраняется их состояние или нет.

И не предполагает, что вы будете их использовать

Гениально. "Чтобы не было проблемы, просто не создавайте её". Начнём с того, что я могу тоже самое воспроизвести и с созданием экземплярного класса. Второй аргумент - при подключении nuget-пакетов вы чаще всего не знаете, что там под капотом, существуют ли статические или экземплярные классы, которые могут сохранить какую-то информацию, связанную с предыдущим вызовом функции. И да - повторю свой вопрос. Цель статьи - показать проблему со stateless в реализации Яндекс Функций и предложить решение, как с этим можно побороться. Каким образом ваше решение помогает бороться с этой проблемой?

Если вам не нужны сервисы, то вы их просто не регистрируйте

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

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

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

Мне кажется, вы как-то не так понимаете stateless/serverless. Из ваших претензий я почерпываю следующее:

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

  2. Вы также хотите, чтобы стартап "stateless" происходил максимально быстро и негодуете на медленную скорость подъёма приложения.

Но я не вижу, как именно вы подразумеваете, как это должно произойти. Возможно, через множество подготовленных одноразовых аппдоменов? Но ведь это же ужасно расточительно, тратить 100+мс процессора, чтобы выполнить один очень быстрый и не требовательный ресурс.

Еще одно противоречение вижу в ваших словах: вы противитесь не использованию стататичных классов, но топите за "stateless". Закономерный вопрос - нафига вам статичные классы, если, по вашей логике, в "stateless" вы ожидаете, что они будут постоянно обнуляться (что довольно дорого со стороны хоста)? У вас есть ваш хендлер-метод, есть весь контекст запроса, зачем вам статика?

Понимаете к чему я, или начнёте спрашивать, мол, ну а нестатичная переменная типа тоже почему не обнулялась?

Вы хотите, чтобы все статические классы магически обнулились.

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

Вы также хотите, чтобы стартап "stateless" происходил максимально быстро и негодуете на медленную скорость подъёма приложения

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

Но я не вижу, как именно вы подразумеваете, как это должно произойти.

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

Еще одно противоречение вижу в ваших словах: вы противитесь не использованию стататичных классов, но топите за "stateless". Закономерный вопрос - нафига вам статичные классы, если, по вашей логике, в "stateless" вы ожидаете, что они будут постоянно обнуляться (что довольно дорого со стороны хоста)? У вас есть ваш хендлер-метод, есть весь контекст запроса, зачем вам статика?

Понимаете к чему я, или начнёте спрашивать, мол, ну а нестатичная переменная типа тоже почему не обнулялась?

Я понимаю, к чему вы. И хочу, чтобы вы тоже поняли, к чему я. Противоречие вы видите потому, что в статье и своих комментариях я никак не оцениваю использование статических классов. Я просто указываю на тот факт, что они существуют, могут быть написаны не нами и не быть нам подконтрольными, т.е. существовать внутри nuget-пакета, например. И если такой пакет подключен, скажем, к классическому ASP.NET Core решению, то у меня нет никаких вопросов к тому, что он хранит какое-то состояние. Так как нам никто и не обещал, что ASP.NET Core на уровне архитектуры является stateless. Может там наоборот, что-то закешировать надо и это фича, а не баг.

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

Я просто указываю на тот факт, что они существуют, могут быть написаны не нами и не быть нам подконтрольными, т.е. существовать внутри nuget-пакета, например

В таком случае, это плохие пакеты, которые плохо спроектировали. Подобный аргумент можно и раздуть дальше — например, можно сказать, что, могут существовать такие пакеты, которые складывают статичные данные вообще в файл рядом с собой. Или еще куда. Что вы будете с ними делать, вмето пересоздания процесса перезапускать весь контейнер? Или всё же признаете, что это не проблема stateless хоста?

В таком случае, это плохие пакеты, которые плохо спроектировали.

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

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

Могут. А ещё существуют базы данных, вы не поверите. И в них тоже можно писать информацию. Только как это всё вообще относится к тому, что написано в статье и что обсуждалось изначально? Давайте ещё раз попробую свою мысль донести: сохранение информации во внешний источник - это ок. Сохранение информации в каких-то классах, будь они статичными или нет - это тоже ок, классы для того и созданы, чтобы переносить и хранить информацию. Можно ли создавать статический класс внутри nuget-пакета? Да, технология позволяет. Не ок начинается тогда, когда облачная функция, которая должна быть stateless по определению, перестаёт быть таковой. Чтобы определить, соблюдается ли это в Яндекс Функциях, я использовал статический класс. Я не говорю, что надо везде использовать статический класс, сохранять в него информацию и потом жаловаться, что она там есть. Это искусственный пример, который в моих экспериментах является маркером того, запускается ли приложение внутри облачной функции при каждом вызове заново или мы продолжаем стрелять в поднятый ранее хост. Если можете предложить свой способ тестировать такое - предлагайте, буду рад. Никакого другого мнения вы от меня не получите, я предлагаю перестать цепляться к статическим файлам и тем более обсуждать тут хорошие/плохие практики их использования. Если хотите похоливарить по этому поводу - думаю, можно найти более подходящую по тематике публикацию и более охотливого оппонента, мне не интересно.

Или всё же признаете, что это не проблема stateless хоста?

О, а вот тут я готов с вами согласиться и закончить этот бессмысленный флуд. Вы считаете, что отсутствие stateless на уровне архитектуры облачных функций это не проблема? Прекрасно, имеете право на своё мнение. Более того, разработчики яндекса, развивающие .NET среду для облачных функций, явно считают также. Может быть вы даже один из них. Если нет - можно попробовать получить там оффер.

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

Интересно сравнить с другими, аналогичными, сервисами. Может, там так же работает?

Ценное замечание, спасибо!
Действительно, как минимум в некоторых других популярных решениях с .NET похожая ситуация. По крайней мере так было раньше. Я осознанно не включал это в разбор, чтобы не сводить дискуссию до уровня "Если сосед обосрался, то и мне можно". Цель публикации — подсветить конкретные проблемы с .NET на конкретной платформе. Если мои решения помогут при работе с другими платформами — так даже лучше.

А вообще было бы интересно посмотреть на сравнительный анализ запуска .NET-приложений в разных сервисах, согласен.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории