views:

434

answers:

4

Summary: We're having problems with EF4 query compilation times of 12+ seconds. Cached queries will only get us so far; are there any ways we can actually reduce the compilation time? Is there anything we might be doing wrong we can look for? Thanks!

We have an EF4 model which is exposed over the WCF services. For each of our entity types we expose a method to fetch and return the whole entity for display / edit including a number of referenced child objects.

For one particular entity we have to .Include() 31 tables / sub-tables to return all relevant data. Unfortunately this makes the EF query compilation prohibitively slow: it takes 12-15 seconds to compile and builds a 7,800-line, 300K query. This is the back-end of a web UI which will need to be snappier than that.

Is there anything we can do to improve this? We can CompiledQuery.Compile this - that doesn't do any work until first use and so helps the second and subsequent executions but our customer is nervous that the first usage shouldn't be slow either. Similarly if the IIS app pool hosting the web service gets recycled we'll lose the cached plan, although we can increase lifetimes to minimise this. Also I can't see a way to precompile this ahead of time and / or to serialise out the EF compiled query cache (short of reflection tricks). The CompiledQuery object only contains a GUID reference into the cache so it's the cache we really care about. (Writing this out it occurs to me I can kick off something in the background from app_startup to execute all queries to get them compiled - is that safe?)

However even if we do solve that problem, we build up our search queries dynamically with LINQ-to-Entities clauses based on which parameters we're searching on: I don't think the SQL generator does a good enough job that we can move all that logic into the SQL layer so I don't think we can pre-compile our search queries. This is less serious because the search data results use fewer tables and so it's only 3-4 seconds compile not 12-15 but the customer thinks that still won't really be acceptable to end-users.

So we really need to reduce the query compilation time somehow. Any ideas?

  • Profiling points to ELinqQueryState.GetExecutionPlan as the place to start and I have attempted to step into that but without the real .NET 4 source available I couldn't get very far, and the source generated by Reflector won't let me step into some functions or set breakpoints in them.
  • The project was upgraded from .NET 3.5 so I have tried regenerating the EDMX from scratch in EF4 in case there was something wrong with it but that didn't help.
  • I have tried the EFProf utility advertised here but it doesn't look like it would help with this. My large query crashes its data collector anyway.
  • I have run the generated query through SQL performance tuning and it already has 100% index usage. I can't see anything wrong with the database that would cause the query generator problems.
  • Is there something O(n^2) in the execution plan compiler - is breaking this down into blocks of separate data loads rather than all 32 tables at once likely to help? Setting EF to lazy-load didn't help.
  • I've bought the pre-release O'Reilly Julie Lerman EF4 book but I can't find anything in there to help beyond 'compile your queries'.

I don't understand why it's taking 12-15 seconds to generate a single select across 32 tables so I'm optimistic there's some scope for improvement!

Thanks for any suggestions! We're running against SQL Server 2008 in case that matters and XP / 7 / server 2008 R2 using RTM VS2010.

+1  A: 

This is likely not the answer you are looking for, but for a simple workaround why don't you run the CompiledQuery.Compile at the time the webapp gets initialized (do some dummy call to the webapp) instead of the first (customer) call.

Foxfire
Thanks. Yes, that's worth trying provided I can do it in the background - I don't want to lock up the site for a few minutes compiling all of the queries every time the app pool restarts. However it won't help speed up our search queries which we construct on-the-fly and so can't cache as far as I can see.(The CompiledQuery.Compile call actually just associates the query with a cache GUID and returns immediately - it's not until first execution that the compile actually happens. So I'd have to do a dummy fetch on each compiled query at start-up to trigger compilation.)
Rup
We implemented this as a background thread on app init and it doesn't always seem to work unfortunately; it works fine in a single-thread case and there's no thread-dependency in the EF4 caching code so I'm at a bit of a loss as to why it doesn't always work. However this does help in some cases so we've stuck with it. Thanks for the suggestion!
Rup
+2  A: 

Make your queries simpler. Seriously; there's an almost linear relationship between query complexity and compile time. Two simple queries are often much faster than one really complicated query (even if precompiled!). If speed is the end goal, choose the fastest option.

Craig Stuntz
Thanks, and sorry for taking ages to come back to this. Yes, this is what we've ended up doing more-or-less for read-for-display cases: breaking object loads into several smaller chunks with precompiled queries for each and then recombining the data retrieved into the POCO objects we were using for data transport. For safety we've stuck to single large queries for the read-for-update cases rather than trying to stitch the EF data back together for the combined result object.
Rup
+2  A: 

You could create a view for some of your more complex queries which gives you complete control of the SQL. Then include that view in your data model.

Tony
Thanks, and sorry for taking ages to come back to this. Yes, we ended up creating views for some of the read-only queries (search results etc.) but we stuck to building up object trees for general object fetches.
Rup
+1  A: 

You can try to use http://www.iis.net/download/ApplicationWarmup It is in beta now, but we regarding to use it too.

mmcteam.com.ua
Thanks for the suggestion, and sorry for taking ages to come back to this.The powers-that-be have said no, at least not whilst it's still a beta, so we've ended up doing something similar in a background thread - unfortunately with limited success :-/
Rup