Lazy Queries

In Lazy Querying mode objects are not evaluated at all, instead of this an iterator is created against the best index found. Further query processing (including all index processing) will happen when the user application iterates through the resulting ObjectSet. This allows you to get the first query results almost immediately.

QueryModesExample.cs: TestLazyQueries
01private static void TestLazyQueries() 02 { 03 Console.WriteLine("Testing query performance on 10000 pilot objects in Lazy mode"); 04 FillUpDB(10000); 05 IConfiguration configuration = Db4oFactory.NewConfiguration(); 06 configuration.Queries().EvaluationMode(QueryEvaluationMode.LAZY); 07 IObjectContainer db = Db4oFactory.OpenFile(configuration, Db4oFileName); 08 try 09 { 10 //QueryStats stats = new QueryStats(); 11 //stats.connect(db); 12 IQuery query = db.Query(); 13 query.Constrain(typeof(Pilot)); 14 query.Descend("_points").Constrain(99).Greater(); 15 DateTime dt1 = DateTime.UtcNow; 16 query.Execute(); 17 DateTime dt2 = DateTime.UtcNow; 18 TimeSpan diff = dt2 - dt1; 19 Console.WriteLine("Query execution time=" + diff.Milliseconds + " ms"); 20 } 21 finally 22 { 23 db.Close(); 24 } 25 }

QueryModesExample.vb: TestLazyQueries
01Private Shared Sub TestLazyQueries() 02 Console.WriteLine("Testing query performance on 10000 pilot objects in Lazy mode") 03 FillUpDB(10000) 04 Dim configuration As IConfiguration = Db4oFactory.NewConfiguration() 05 configuration.Queries.EvaluationMode(QueryEvaluationMode.LAZY) 06 Dim db As IObjectContainer = Db4oFactory.OpenFile(configuration, Db4oFileName) 07 Try 08 Dim query As IQuery = db.Query 09 query.Constrain(GetType(Pilot)) 10 query.Descend("_points").Constrain(99).Greater() 11 Dim dt1 As DateTime = DateTime.UtcNow 12 query.Execute() 13 Dim dt2 As DateTime = DateTime.UtcNow 14 Dim diff As TimeSpan = dt2 - dt1 15 Console.WriteLine("Query execution time=" + diff.Milliseconds.ToString() + " ms") 16 Finally 17 db.Close() 18 End Try 19 End Sub

In addition to the very fast execution this method also ensures very small memory consumption, as lazy queries do not need an intermediate representation as a set of IDs in memory. With this approach a lazy query ObjectSet does not have to cache a single object or ID. The memory consumption for a query is practically zero, no matter how large the resultset is going to be.

There are some interesting effects appearing due to the fact that the objects are getting evaluated only on a request. It means that all the committed modifications from the other transactions and uncommitted modifications from the same transaction will be taken into account when delivering the result objects:

QueryModesExample.cs: TestLazyConcurrent
01private static void TestLazyConcurrent() 02 { 03 Console.WriteLine("Testing lazy mode with concurrent modifications"); 04 FillUpDB(10); 05 IConfiguration configuration = Db4oFactory.NewConfiguration(); 06 configuration.Queries().EvaluationMode(QueryEvaluationMode.LAZY); 07 IObjectContainer db = Db4oFactory.OpenFile(configuration, Db4oFileName); 08 try 09 { 10 IQuery query1 = db.Query(); 11 query1.Constrain(typeof(Pilot)); 12 query1.Descend("_points").Constrain(5).Smaller(); 13 IObjectSet result1 = query1.Execute(); 14 15 IQuery query2 = db.Query(); 16 query2.Constrain(typeof(Pilot)); 17 query2.Descend("_points").Constrain(1); 18 IObjectSet result2 = query2.Execute(); 19 Pilot pilotToDelete = (Pilot)result2[0]; 20 Console.WriteLine("Pilot to be deleted: " + pilotToDelete); 21 db.Delete(pilotToDelete); 22 Pilot pilot = new Pilot("Tester", 2); 23 Console.WriteLine("Pilot to be added: " + pilot); 24 db.Set(pilot); 25 26 Console.WriteLine("Query result after changing from the same transaction"); 27 ListResult(result1); 28 } 29 finally 30 { 31 db.Close(); 32 } 33 }

QueryModesExample.vb: TestLazyConcurrent
01Private Shared Sub TestLazyConcurrent() 02 Console.WriteLine("Testing lazy mode with concurrent modifications") 03 FillUpDB(10) 04 Dim configuration As IConfiguration = Db4oFactory.NewConfiguration() 05 configuration.Queries.EvaluationMode(QueryEvaluationMode.LAZY) 06 Dim db As IObjectContainer = Db4oFactory.OpenFile(configuration, Db4oFileName) 07 Try 08 Dim query1 As IQuery = db.Query 09 query1.Constrain(GetType(Pilot)) 10 query1.Descend("_points").Constrain(5).Smaller() 11 Dim result1 As IObjectSet = query1.Execute 12 Dim query2 As IQuery = db.Query 13 query2.Constrain(GetType(Pilot)) 14 query2.Descend("_points").Constrain(1) 15 Dim result2 As IObjectSet = query2.Execute 16 Dim pilotToDelete As Pilot = CType(result2(0), Pilot) 17 Console.WriteLine("Pilot to be deleted: " + pilotToDelete.ToString()) 18 db.Delete(pilotToDelete) 19 Dim pilot As Pilot = New Pilot("Tester", 2) 20 Console.WriteLine("Pilot to be added: " + pilot.ToString()) 21 db.Set(pilot) 22 Console.WriteLine("Query result after changing from the same transaction") 23 ListResult(result1) 24 Finally 25 db.Close() 26 End Try 27 End Sub

Pros and Cons for Lazy Queries 

Pros:

  • The call to Query.execute() will return very fast. First results can be made available to the application before the query is fully processed.
  • A query will consume hardly any memory at all because no intermediate ID representation is ever created.

Cons:

  • Lazy queries check candidates when iterating through the resulting ObjectSet. In doing so the query processor takes changes into account that may have happened since the Query.execute() call: committed changes from other transactions, and uncommitted changes from the calling transaction. There is a wide range of possible side effects:
    • The underlying index may have changed.
    • Objects themselves may have changed in the meanwhile.
    • There even is a chance of creating an endless loop. If the caller iterates through the ObjectSet and changes each object in a way that it is placed at the end of the index, the same objects can be revisited over and over.
    In lazy mode it can make sense to work in a way one would work with collections to avoid concurrent modification exceptions. For instance one could iterate through the ObjectSet first and store all the objects to a temporary collection representation before changing objects and storing them back to db4o.
  • Some method calls against a lazy ObjectSet will require the query processor to create a snapshot or to evaluate the query fully. An example of such a call is ObjectSet.size().

Lazy mode can be an excellent choice for single transaction read use, to keep memory consumption as low as possible.