Как стать автором
Обновить
24
0
Антон Широких @MrPomidor

Пользователь

Отправить сообщение

На счет POST это мое упущение, почему то забыл добавить этот сценарий :) Исправил в статье и материалах. К сожалению проблема скорее всего именно в EF change tracking как для POST так и для PUT, потому что raw sql код EF работает так же быстро как и код на Dapper. Будем ждать следующего релиза, там как раз обещают оптимизировать эти сценарии.

P.S.: AsNoTracking указывается для всего запроса и согласно описанию:

The change tracker will not track any of the entities that are returned from a LINQ query.

P.P.S.: Очень хороший поинт ! Я думал о том чтобы включить пункт про сплит в статью, но к сожалению мне не хватило усидчивости :) К тому же, на мой взгляд, тут нужно подходить больше со стороны БД чем EF. Было бы здорово если бы кто то смог осветить тему эффективности разделения запросов в отдельной статье.

UPD: ошибся на счет 3-х минут, каждый сценарий работал 30 секунд :)

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

Отвечал в комментарии выше, но на всякий случай продублирую:

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

Формулировка с синглтоном присутствует и в документации к EF, я привел эту формулировку почти дословно:

Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes).

Для всех сценариев из статьи, SQL код, который генерирует EFCoreImprovedProductsRepository, идентичен коду из DapperProductsRepository.

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

Зачем, можно ведь использовать `dbContext.Database.ExecuteSqlRaw` который работает с такой же производительностью :)

  1. Уточнение: в тестовом сценарии нам необходимо отредактировать только Name, поэтому мы намеренно используем `IsModified = true` только для этого свойства. При использовании `IsModified = true` EF сгенерирует код идентичный коду в Dapper варианте репозитория, где Update применяется только для свойства Name. Если попытаться использовать в данном сценарии `dbContext.Entry(entity).State = EntityState.Modified`, EF попытается в Update обновить все свойства в БД из обьекта, большинство из которых будут null.

  2. Много слышал про Linq2Db, наверное стоит попробовать) Спасибо !

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

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

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

  2. Не поддерживает работу на машине с одним логическим процессором (в случае если вы выделите на ваш контейнер один логический процессор).

Приведу несколько цитат где это описано более детально:

Server garbage collection can be resource-intensive. For example, imagine that there are 12 processes that use server GC running on a computer that has four logical CPUs. If all the processes happen to collect garbage at the same time, they would interfere with each other, as there would be 12 threads scheduled on the same logical CPU. If the processes are active, it's not a good idea to have them all use server GC.

If you're running hundreds of instances of an application, consider using workstation garbage collection with concurrent garbage collection disabled. This will result in less context switching, which can improve performance.

https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/workstation-server-gc#server-gc

Server GC was designed with the assumption that the process using Server GC is the dominant process on the machine.

https://devblogs.microsoft.com/dotnet/running-with-server-gc-in-a-small-container-scenario-part-0/

Спасибо за хороший комментарий !

Если я правильно понял то что вы описали, вам нужен сценарий с 3-я степами с приблизительно вот таким псевдокодом:

var loginStep = Step.Create("auth", (context) => {
  var authData = httpClient.Authorize();
  context.Data["authData"] = authData;
  return Response.Ok();
});
var getCollectionStep = Step.Create("getCollection", (context) => {
  var authData = context.Data["authData"];
  var collection = httpClient.GetCollection(authData);
  context.Data["collection"] = collection;
  return Response.Ok();
});
var iterateCollectionStep = Step.Create("iterate", (context) => {
  var collection = context.Data["collection"];
  foreach (var item in collection) {
    // do some work here 
  }
  return Response.Ok();
});

var scenario = ScenarioBuilder.CreateScenario("yourComplexScenario",
                                             loginStep,
                                             getCollectionStep,
                                             iterateCollectionStep);

NBomberRunner
    .RegisterScenarios(scenario)
    .Run();

Для сбора метрик из системы вам нужны инструменты мониторинга, такие как Application Insights, dotMemory, PerfView, dotnet counters, логи и так далее.

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

Если бы я программировал на Java/Scala/Kotlin, я бы рекомендовал Gatling ровно по тем же причинам, по которым рекомендую NBomber - удобство работы в привычном для вас стеке технологий.
Я уверен что все инструменты, и JMeter и Gatling и NBomber выполняют свои задачи и вряд ли делают что то нетривиальное. Как я и написал, вопрос во времени, которое вы готовы выделить на обучение (можно и Java и C# выучить при желании) и вопрос в вашем комфорте от работы в привычной среде.

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

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность