Использование NHibernate для индексации больших объемов данных в Lucene.Net - PullRequest
1 голос
/ 28 июля 2010

Мы используем Nhibernate в качестве слоя доступа к данным.У нас есть таблица из 1,7 миллиона записей, которые нам нужно индексировать по очереди через Lucene для нашего поиска.Когда мы запускаем консольное приложение, которое мы написали для построения нашего индекса, оно начинается быстро, но по мере прохождения пунктов оно постепенно становится все медленнее и медленнее.

Наша первая итерация состояла в том, чтобы просто проиндексировать их все.Вторая итерация заключалась в индексации их по категориям.Теперь мы выбираем подмножества по категориям, а затем разбиваем их на «страницы» по 100. У нас все еще есть ухудшение производительности.

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

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

Вот наше отображение:

public class ItemMap : ClassMap<Item>
    {
        public ItemMap()
        {
            Table("Products");

            Id(x => x.Id, "ProductId").GeneratedBy.GuidComb();

            Map(x => x.Model);
            Map(x => x.Description);

            Map(x => x.Created);
            Map(x => x.Modified);
            Map(x => x.IsActive);
            Map(x => x.PurchaseUrl).CustomType<UriType>();

            Component(x => x.Identifier, m =>
                {
                    m.Map(x => x.Upc);
                    m.Map(x => x.Asin);
                    m.Map(x => x.Isbn);
                    m.Map(x => x.Tid);
                });

            Component(x => x.Price, m =>
                {
                    m.Map(x => x.Currency);
                    m.Map(x => x.Amount, "Price");
                    m.Map(x => x.Shipping);
                });

            References(x => x.Brand, "BrandId");
            References(x => x.Category, "CategoryId");
            References(x => x.Supplier, "SupplierId");
            References(x => x.Provider, "ProviderId");

            HasMany(x => x.Images)
                .Table("ProductImages")
                .KeyColumn("ProductId")
                .Not.LazyLoad();




            // TODO: Add variants





        }

    }

А вот корневая логика приложения индексирования.

public void IndexProducts()
        {
            Console.WriteLine("--- Begin Indexing Products ---");
            Console.WriteLine();
            var categories = categoryRepository.GetAll().ToList();
            Console.WriteLine(String.Format("--- {0} Categories found ---", categories.Count));
            categories.Add(null);

            foreach (var category in categories)
            {
                string categoryName = "\"None\"";

                if (category != null)
                    categoryName = category.Name;

                Console.WriteLine(String.Format("--- Begin Indexing Category ({0}) ---", categoryName));
                var categoryItems = from p in catalogRepository.GetList(new ActiveProductsByCategoryQuery(category))
                                    select p;

                int count = categoryItems.Count();
                int pageSize = 100;
                int currentPage = 0;
                int offest = currentPage * pageSize;
                int current = 1;

                Console.WriteLine(String.Format("Indexing {0} Products...", count));

                while (offest < count)
                {
                    var products = (from p in categoryItems
                                    select p).Skip(offest).Take(pageSize);

                    foreach (var item in products)
                    {
                        indexer.UpdateContent(item);
                        UpdateCounter(current, count);
                        current++;
                    }

                    currentPage++;
                    offest = currentPage * pageSize;
                }
                Console.WriteLine();

                Console.WriteLine(String.Format("--- End Indexing Category ({0}) ---", categoryName));
                Console.WriteLine();
            }

            Console.WriteLine("--- End Indexing Products ---");
            Console.WriteLine();
        }

К вашему сведению, для рассматриваемой категории количество составляет 26552. Первый запрос, который он запускает:

exec sp_executesql N'SELECT TOP 100 ProductId100_1_, Upc100_1_, Asin100_1_, Isbn100_1_, Tid100_1_, Currency100_1_, Price100_1_, Shipping100_1_, Model100_1_, Descrip10_100_1_, Created100_1_, Modified100_1_, IsActive100_1_, Purchas14_100_1_, BrandId100_1_, CategoryId100_1_, SupplierId100_1_, ProviderId100_1_, CategoryId103_0_, Name103_0_, ShortName103_0_, Created103_0_, Modified103_0_, ShortId103_0_, DisplayO7_103_0_, IsActive103_0_, ParentCa9_103_0_ FROM (SELECT this_.ProductId as ProductId100_1_, this_.Upc as Upc100_1_, this_.Asin as Asin100_1_, this_.Isbn as Isbn100_1_, this_.Tid as Tid100_1_, this_.Currency as Currency100_1_, this_.Price as Price100_1_, this_.Shipping as Shipping100_1_, this_.Model as Model100_1_, this_.Description as Descrip10_100_1_, this_.Created as Created100_1_, this_.Modified as Modified100_1_, this_.IsActive as IsActive100_1_, this_.PurchaseUrl as Purchas14_100_1_, this_.BrandId as BrandId100_1_, this_.CategoryId as CategoryId100_1_, this_.SupplierId as SupplierId100_1_, this_.ProviderId as ProviderId100_1_, category1_.CategoryId as CategoryId103_0_, category1_.Name as Name103_0_, category1_.ShortName as ShortName103_0_, category1_.Created as Created103_0_, category1_.Modified as Modified103_0_, category1_.ShortId as ShortId103_0_, category1_.DisplayOrder as DisplayO7_103_0_, category1_.IsActive as IsActive103_0_, category1_.ParentCategoryId as ParentCa9_103_0_, ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row FROM Products this_ left outer join Categories category1_ on this_.CategoryId=category1_.CategoryId WHERE (this_.IsActive = @p0 and (1=0 or (this_.CategoryId is not null and category1_.CategoryId = @p1)))) as query WHERE query.__hibernate_sort_row > 500 ORDER BY query.__hibernate_sort_row',N'@p0 bit,@p1 uniqueidentifier',@p0=1,@p1='A988FD8C-DD93-4119-8F84-0AF3656DAEDD'

Затем для каждого продукта выполняется

exec sp_executesql N'SELECT images0_.ProductId as ProductId1_, images0_.ImageId as ImageId1_, images0_.ImageId as ImageId98_0_, images0_.Description as Descript2_98_0_, images0_.Url as Url98_0_, images0_.Created as Created98_0_, images0_.Modified as Modified98_0_, images0_.ProductId as ProductId98_0_ FROM ProductImages images0_ WHERE images0_.ProductId=@p0',N'@p0 uniqueidentifier',@p0='487EA053-4DD5-4EBA-AA36-95B30C42F0CD'

Это нормально. Проблема в том, что первые 2000 или около того действительно быстрые, но чем дольше он проходит через категорию, тем медленнее он получает и больше памяти он потребляет - даже при том, что он индексирует то же количество продуктов. GC работаетиз-за того, что использование памяти падает, но в целом он увеличивается по мере работы процессора.

Есть ли что-то, что мы можем сделать, чтобы ускорить индексатор? Почему его производительность постоянно снижается? Я не думаю, что это nhibernate илизапросы, потому что он начинается так быстро. Мы действительно в растерянности.

Спасибо

Ответы [ 3 ]

3 голосов
/ 28 июля 2010

У Айенде была запись о том, как это сделать (используя сеанс без сохранения состояния и пользовательскую реализацию IList) всего пару недель назад.

http://ayende.com/Blog/archive/2010/06/27/nhibernate-streaming-large-result-sets.aspx

Это звучит как то, что вам нужноПо крайней мере для ускорения извлечения записей и минимизации использования памяти.

0 голосов
/ 27 августа 2010

Мы закончили тем, что перешли на Solr для нашей индексации. Мы никогда не смогли заставить его эффективно индексировать, что, вероятно, связано с реализацией.

Для справки:

http://lucene.apache.org/solr/

http://code.google.com/p/solrnet/

0 голосов
/ 28 июля 2010

Используете ли вы один и тот же сеанс для всех вызовов?Если это так, он будет кэшировать загруженные объекты и проходить через них, чтобы проверить, нужно ли их очищать при вызове Flush (что зависит от вашего FlushMode).Либо используйте новый сеанс для каждой страницы элементов, либо измените FlushMode.

При использовании критериев можно указать, что определенные свойства должны быть предварительно выбраны с помощью объединения SQL, что может ускорить чтение данных.Я обычно доверяю critiera apis больше, чем Linq-to-NHibernate, просто потому, что я на самом деле решаю, что делается для каждого звонка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...