Страницы

вторник, 1 мая 2012 г.

NHibernate Future – оптимизация сетевых издержек.

  Куда уходит время.

  Думаю многие знакомы с проблемами распределённых вычислений. Межпроцессные вызовы выполняются на 3-4 порядка медленнее внутрипроцессных. Межмашинные вызовы длятся на 5-6 порядков медленнее внутрипроцессных. Сетевые  издержки накладываются на каждый межмашинный вызов, приводят к бесполезным потерям времени ожидающими потоками. Напрасный расход машинных ресурсов. Эта проблема была изначальным стимулом к созданию DTO-объектов – вместо многих вызовов, запрашивающих по чуть-чуть, запросить сразу все необходимые данные.

  Эта проблема знакома и создателям NHibernate. NH старается как можно реже обращаться к базе данных, откладывая запросы на последний момент,  выполняет их в пакетном режиме по возможности. Впрочем, в некоторых случаях мы всё равно вынуждены брать контроль в свои руки.  Один из таких случаев – управление временем выполнения запроса (моментом, когда запрос необходимо отправить в базу данных). По понятным причинам, создать DTO-объект для работы с базой данных невозможно. Но и не нужно – NHibernate уже предоставляет хорошее API для этих целей.

  Запроси завтра то, что можно запросить сегодня.

  Цель – уменьшить количество вызовов  к базе данных. Для этого в NHibernate есть специальное API – Futures. В каждом API (IQuery, ISQLQuery, ICriteria, IQueryOver) есть два специальных метода Future и FutureValue:

var count = session.CreateQuery("select count(*) from Cat c").FutureValue<long>();
var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").Future<Cat>();

  Оба метода на самом деле не выполняют запрос, а лишь добавляют его к списку запросов, которые будут выполнены  потом. “Потом” наступает в момент перечисления коллекции (Future) или получения единственное значение (FutureValue). Все запросы будут отправлены в базу данных пачкой за один вызов.


  Несмотря на явные преимущества, в ответ на предложение использовать Future,  я слышал в ответ нечто вроде “А зачем? Не так уж дорого сделать еще один запрос в базу”. Насколько “дёшево” сходить еще раз в базу я и решил проверить численно. На этот раз мне было лень выдумывать “примеры из жизни”, поэтому будет тренироваться на абстрактных кошках, и сравнивать время выполнения двух методов:

private void QueryDirectly(string timeRegion)
{
    using (var session = _factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        using (StopwatchManager.Start(timeRegion, elapsed => Console.WriteLine("{0} total time: {1}", timeRegion, elapsed.Total)))
        {
            var count = session.CreateQuery("select count(*) from Cat c").UniqueResult<long>();
            var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").List<Cat>();
            var bazil = session.CreateQuery("from Cat c where c.Name = 'Bazil'").UniqueResult<Cat>();
        }
        tx.Commit();
    }
}
private void QueryUsingFuture(string timeRegion)
{
    using (var session = _factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        using (StopwatchManager.Start(timeRegion, elapsed => Console.WriteLine("{0} total time: {1}", timeRegion,  elapsed.Total)))
        {
            var count = session.CreateQuery("select count(*) from Cat c").FutureValue<long>();
            var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").Future<Cat>();
            var bazil = session.CreateQuery("from Cat c where c.Name = 'Bazil'").FutureValue<Cat>();
            var value = count.Value;
        }
        tx.Commit();
    }
}

  Перед запуском на время, каждый метод прогоняется отдельно, чтобы исключить влияние JIT-компилятора. После этого оба метода выполняются в цикле по 1000 раз, и c помощью Stopwatch замерялось время выполнения только самих запросов. Итак, результаты:


image


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


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


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

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

Отправить комментарий