Ленивая загрузка в сложных объектах RavenDb

Похоже, мы имеем дело с плохо продуманной проблемой объектного дизайна, которая в настоящее время проявляется в значительных проблемах производительности / памяти.

У нас есть тысячи корневых агрегатных объектов, хранящихся в базе данных RavenDb. Для некоторых крупных клиентов эти объекты становятся слишком большими, чтобы эффективно выполнять веб-операции (открытие страниц, сохранение данных и т. Д.).

Структура выглядит следующим образом: объект учетной записи является совокупным корнем. Под ним находится множество небольших объектов и коллекций, которые имеют "прекрасный" размер, за исключением одной коллекции под названием "Ресурсы", которая может стать очень большой и привести к тому, что корневые объекты будут несколько мегабайт в размере. Это приводит к тому, что базовые операции CRUD в учетной записи и ее внутренних данных выполняются очень медленно

Объекты в коллекции Resource сами по себе не очень большие, но у них есть собственные дети, и они растягивают размер вверх. Каждый объект ресурса имеет метрики, действия, оповещения, масштабирование и другие "тяжелые" коллекции.

Наша кодовая база очень сложна с сотнями тысяч строк кода; сотни, если не тысячи строк кода ссылаются на коллекцию ресурсов и проверяют объекты ресурсов внутри нее, но доступ к базовым дочерним коллекциям каждого объекта ресурса представляется нечастым и в основном выполняется по одному ресурсу за раз

Вопрос: Как мы загружаем объект Account, все его дочерние и другие объекты и только первый уровень объектов Resource, а затем дочерние элементы lazy-load для Resources? (есть как 7 определенных коллекций, которые могут быть загружены ленивыми)

У нас есть один репозиторий, который отвечает за загрузку / сохранение данных

2 ответа

Как мы загружаем объект Account, все его дочерние и другие объекты и только первый уровень объектов Resource, а затем дочерние элементы lazy-load в Resources? (есть как 7 определенных коллекций, которые могут быть загружены ленивыми)

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

До:

class Resource
{
   public List<Foo> Foos { get; set; }
   public List<Bar> Bars { get; set; }
   // ... etc
}

После:

class Resource
{
   // These are the things we need to lazy load.
   public List<string> FooIds { get; set; }
   public List<string> BarIds { get; set; }
}

Что касается ваших объектов Foo и Bar (ленивые загруженные дочерние элементы Resource), вам нужно. Сохранить их как свои собственные документы.

Как только вы это сделаете, загрузка ресурса не загрузит все его дочерние объекты, что даст вам преимущества при чтении и записи.

Но как насчет того, когда вам нужно загрузить этих детей? Используйте.Include:

// Query for Resource and include the children in a single remote call.
var resourcesWithChildren = docSession
   .Query<Resource>()
   .Include(r => r.FooIds) // Include the related Foos
   .Include(r => r.BarIds) // Include the related Bars
   .Where(...)
   .ToList();


foreach (var resource in resourcesWithChildren)
{
    // Grab the children; they're already loaded, so this won't induce a remote call.
    var foos = docSession.Load<Foo>(resource.FooIds);
    var bars = docSession.Load<Bar>(resource.BarIds);
}

Как мы загружаем объект Account, все его дочерние объекты и объекты, и только первый уровень объектов Resource, а затем дочерние элементы lazy-load для Resources? (есть как 7 определенных коллекций, которые могут быть загружены ленивыми)

Хорошо, мой другой ответ - рекомендуемый способ разбивать огромные объекты; просто сделайте их своими независимыми объектами.

Но, поскольку вы сказали, что не хотите делать работу, чтобы разбить их, есть еще один способ сделать это, используя трансформатор. Использование преобразователя не спасет Raven от загрузки большого объекта Account и всех его дочерних элементов, но, поскольку преобразователь выполняется на сервере, он не отправит огромный объект по сети на ваш веб-сервер.

public class AccountWithFirstLevelResourcesTransformer : AbstractTransformerCreationTask<Account>
{
    public AccountWithFirstLevelResourcesTransformer()
    {
        TransformResults = accs => from acc in accs
                                   select new Account
                                   {
                                       ...
                                       Resources = acc.Resources.Select(fullResource => new Resource
                                       {
                                            // Only the properties we want loaded here.
                                            Name = fullResource.Name,
                                            ...
                                       })
                                       ...
                                   };
    }
}

Вы установите этот трансформатор во время запуска:

new AccountWithFirstLevelResourcesTransformer().Execute(RavenStore); // RavenStore is your IDocumentStore singleton.

Тогда ваши.Load вызовы будут выглядеть так:

// This account will have only the first level resources.
var account = dbSession.Load<AccountWithFirstLevelResourcesTransformer, Account>("accounts/1");
Другие вопросы по тегам