Обработка большого результирующего набора с помощью NHibernate

#c# #performance #nhibernate

#c# #Производительность #nhibernate

Вопрос:

У меня есть следующая задача: рассчитать проценты для всех активных учетных записей. В прошлом я делал подобные вещи с помощью Ado.Сетевые и хранимые процедуры. На этот раз я попытался сделать это с помощью NHibernate, потому что казалось, что сложные алгоритмы будут выполняться проще с чистым POCO. Итак, я хочу сделать следующее (псевдокод):
foreach account in accounts
calculate interest
save account with new interest
Я знаю, что NHibernate не был разработан для обработки больших объемов данных. Для меня достаточно иметь возможность организовать такой цикл, не имея сразу всех учетных записей в памяти. Чтобы минимизировать использование памяти, я бы использовал IStatelessSession for external loop вместо plain ISession . Я попробовал подход, предложенный Айенде. Есть две проблемы:

  • CreateQuery использует «волшебные строки»;
  • что еще более важно: это работает не так, как описано.

Моя программа работает, но после включения трассировки Odbc я увидел в отладчике, что все выборки были выполнены до того, как лямбда-выражение in .List было выполнено в первый раз. Я нашел себе другое решение: session.Query возврат .AsEnumerable() , который я использовал в foreach . Снова две проблемы:

  • Я бы предпочел IQueryOver над IQueryable
  • по-прежнему не работает, как описано (все выборки перед первым вычислением процентов).

Я не знаю, почему, но IQueryOver не имеет AsEnumerable . У него также нет List метода с аргументом (например CreateQuery ). Я пытался .Future , но снова:

  • документация будущего не описывает функцию потоковой передачи
  • по-прежнему не работает так, как мне нужно (все выборки до первого расчета процентов).

В заключение: есть ли какой-либо эквивалент в NHibernate для dataReader.Read() from Ado.Net ?

Моей лучшей альтернативой чистому подходу NHibernate было бы использование основного цикла dataReader.Read() , а затем Load учетная запись с идентификатором из Ado.Сетевой цикл. Однако производительность будет снижаться — чтение каждой учетной записи с помощью ключа выполняется медленнее, чем последовательность выборок, выполняемых во внешнем цикле.

Я использую NHibernate версии 4.0.0.4000.

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

1. вы можете выполнить все обновления за 1 транзакцию, однако сначала вам нужно получить все учетные записи, затем внести изменения, а затем сохранить их одним пакетным вызовом. Я пытался справиться с подобными проблемами раньше, но моей самой большой проблемой обычно была проблема с select-n, которую легко обнаружить, если вы используете sql profiler

2. Транзакция базы данных для меня не проблема. Это может быть одна транзакция для всех учетных записей или для каждой учетной записи отдельно. Я беспокоюсь о потреблении памяти. Я не уверен, что в производственной системе (около 1 миллиона учетных записей) все учетные записи могут быть записаны в память одновременно.

3. Также, согласно Jaguar, производительность снижается, когда количество объектов в памяти превышает некоторый порог.

Ответ №1:

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

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

Итак, вначале вы выполняете запрос count, составляете некоторый произвольный размер пакета, а затем начинаете выполнять свою работу над пакетом. Таким образом, вы можете аккуратно избежать проблемы выбора n 1, предполагая, что для каждого пакета вы явно извлекаете все необходимое.

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

В противном случае вам придется использовать IStatelessSession, хотя там нет ленивых запросов. «из книг» означает «выбрать * из dbo.Книги» или что-то эквивалентное, и все результаты извлекаются в память.

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

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

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