Ayende @ Rahien

It's a girl

Challenges: Where is the optimization?

Let us look at the following pieces of code:

public void Consume(MyBooksRequest message)
{
    var user = session.Get<User>(message.UserId);
    
    bus.Reply(new MyBooksResponse
    {
        UserId = message.UserId,
        Timestamp = DateTime.Now,
        Books = user.CurrentlyReading.ToBookDtoArray()
    });
}

public void Consume(MyQueueRequest message)
{
    var user = session.Get<User>(message.UserId);

    bus.Reply(new MyQueueResponse
    {
        UserId = message.UserId,
        Timestamp = DateTime.Now,
        Queue = user.Queue.ToBookDtoArray()
    });
}

public void Consume(MyRecommendationsRequest message)
{
    var user = session.Get<User>(message.UserId);

    bus.Reply(new MyRecommendationsResponse
    {
        UserId = message.UserId,
        Timestamp = DateTime.Now,
        Recommendations = user.Recommendations.ToBookDtoArray()
    });
}

Looking at this, I see that I have a requirement to getting my books, my queues and my recommendations. Looking at the code, can you guess how many queries are being generated to get those?

And can you suggest an optimization?

Comments

Benny Thomas
02/15/2010 10:23 AM by
Benny Thomas

From my novise point it looks like you have 1 query for the user, and 3 lazyloading query's for the CurrentlyReading, Queue and Recommandations.

You don't need the user info, you should have a specialized query for only the things you need in each Cosume method.

Leonardo
02/15/2010 12:32 PM by
Leonardo

An optimization would be to use a session.Load <user instead of a Get, so you would avoid that extra select as you are only using the ID.

Jason Meckley
02/15/2010 02:00 PM by
Jason Meckley

I think Load would still query for the user,. You need to traverse the user to get to books.

Some approaches for optimization:

  1. Query the books directly rather than lazy loading from User.

  2. Eager load the books with the user

  3. Use Futures to retrieve the user/queue/recommendations at once and cache the results for a short period of time.

option 3 assumes the messages would be used in close succession/proximity to one-another. if they are not this doesn't provide much value. Although, if they were used that closely together, why not load all this information in a single consumer?

Leonardo
02/15/2010 02:41 PM by
Leonardo

I see... I thought Load only triggered the query when navigating to a property that was not an association. Googled a little and found that it triggers the query when accessing to ANY property besides de id.

I agree with those other optimization approaches then.

Hudson Akridge
02/15/2010 11:22 PM by
Hudson Akridge

As Benny Thomas mentions, the lazy loads are going to hurt. Also, I'd imagine that you might encounter additional Select N+1 issues as you attempt to serialize to book dto's unless all of the information required for the serialization is on the object in the collection (which is not a given so I don't want to assume). Should probably join fetch any additional associations you have for those objects either in the mappings (not fantastic) or in the query you'd replace the Get() with.

As to how many queries to get those? Depends on how the ToDto() extension method is implemented. if there's additional associations that are lazy loaded, then it's 3(n), otherwise it should be 6 queries (one each to load the user, one more to load the collection on each).

Alexander Savvas
02/16/2010 12:13 AM by
Alexander Savvas

actually i find it a bit odd that you have a 'UserId" property on the MyXXXRequest classes. You could properly map the user on those, and then eagerly fetch the collections on the user (and eagerly fetching that User on the MyXXXRequest classes). That would lead to no additional queries (but that does not include the passed MyXXXRequest object)

Steve Py
02/16/2010 06:37 AM by
Steve Py

I'd think these are 3 distinct requests so there's no point considering merging the fetches. Lazy-loading is the pain point in this example, you can fetch the associated collection eagerly, but I'd definitely avoid configuring them for eager-fetching, since a call to one method would pull back a lot of extra detail.

IMO though, retrieving the User is not required at all, just retrieve the relevant lists by UserID. (via HQL or Linq2NH) The user can be lazy-loaded should it be needed.

It would also depend on what the To Book DTO functionality was doing to ensure it wasn't attempting to dive the composition.

Comments have been closed on this topic.