Dec 10, 2007

Activation in depth

In this post I'll discuss a key concept of db4o and how it improved in our latest versions: object activation. I'll focus mainly on the .Net version but these concepts apply equally well to the Java version.

In order to access some resources listed in this post you will be required to register (for free) in http://developer.db4o.com.

Activation in depth

Activation in db4o parlance is the process of loading object data from the database to memory. You can see this concept in practice by asking db4o to not activate objects at all and having a look at them. Objects that are not activated will have only its object identity loaded from the database (In order to see the identifier for an object stored in db4o you can
 

IObjectContainer.Ext().GetId(obj);)

After building the graph of candidates (objects that satisfies a query's conditions), db4o needs to know how deep in this graph it must go activating (i.e, reading the object's data into memory). At this point it may seems that we have two contradictory objectives: i) we want to make db4o as transparent as possible to developers (making it easier to be used); ii) we do want to make sure that db4o will use computational resources (in this case memory) as efficiently as possible.

The simple approach to achieve i is to activate the whole graph but this clearly contrasts to ii because it may exhaust all free memory if the query returned a lot of objects. By the other hand, the easiest way to achieve ii (from the perspective of memory utilization) is to read only the identity of the objects in the graph and
leave to the developer the responsibility to activate them when needed. Again, this strategy goes against the other one (i.e, simplicity of use). db4o latest versions includes a new functionality (conceptually called Transparent Activation, or simple TA for brevity) aimed to achieve both goals (i and ii) without sacrificing each other.

Consider the following classes:

public class Person : SupportObjectId
{
    private Address address;

    public Address Address
    {
        get { return address; }
        set { address = value; }
    }

    private string name;

    public string Name
   {
        get { return name; }
        set { name = value; }
   }

   private int age;

   public int Age
  {
      get { return age; }
     set { age = value; }
  }

   private Person parent;

   public Person Parent
   {
       get { return parent; }
       set { parent = value; }
   }

   public Person(string name, int age, Address address, Person parent)
   {
        this.name = name;
        this.address = address;
        this.age = age;
        this.parent = parent;
    }

   public override string ToString()
   {
       return PrintInfo(0);
   }

   private string PrintInfo(int i)
   {
       string tab = new string('\t', i);
      return string.Format("{0} {4} {1}{5}\r\n{4}{4}Address: {2}\r\n{4}{4}Parent: {3}",
                                        (name ?? "null"),
                                        age,
                                        (address != null ? address.ToString() : "null"),
                                        (parent != null ? parent.PrintInfo(i+1) : "null"),
                                       tab,
                                      base.ToString());
    }
}

public class Address : SupportObjectId
{
    private string street;
    public string Street
    {
         get { return street; }
         set { street = value; }
    }

    private int number;
    public int Number
    {
        get { return number; }
        set { number = value; }
    }

    private string city;
    public string City
   {
        get { return city; }
        set { city = value; }
   }

    public Address(string city, string street, int number)
   {
        this.city = city;
        this.street = street;
       this.number = number;
   }

   public override string ToString()
   {
        return "[" + (street ?? "**") + "(" + number + ") - " + (city ?? "**") + base.ToString() + "]" ;
   }
}


and the following objects in the database:


private static void InsertData()
{
     using (IObjectContainer db = Db4oFactory.OpenFile(DATABASE_FILE))
    {
         Person grandParent = new Person("Joseph", 65, new Address("Descalvado", "JV street", 160), null);
         Person father = new Person("Adrian", 36, new Address("São Paulo", "C. L. E. M. street", 250), grandParent);
         Person child = new Person("Caroline", 4, new Address("São Paulo", "C. L. E. M. street", 250), father);

         db.Set(child);
    }
}

The following image shows the result when querying for "Car
oline" at different activation depths (with Transparent Activation disabled):


Note that at depth 0 (zero) only the object identifier for "Caroline" (1336) was loaded from the database; at depth 1 Caroline's value types were fully loaded and reference fields were initialized (to null) but not activated (see Caroline's address and parent fields). At depth 2 Caroline's parent object was also loaded (and activated) but its reference fields (for instance Caroline's grand father) were not activated yet.

Related Concepts

The same trade-of (simplicity/performance) applies when updating objects in a db4o database; I mean, usually you'll not want to update the whole set of objects reachable from the one you are currently updating because this would probably end up updating a lot of objects needlessly. To control this behavior you may configure the update depth through IConfiguration.UpdateDepth() method which takes an integer meaning how deep db4o will go when updating objects (keep in mind that when inserting, db4o will follow all references and insert all of then). This configuration may be globally (i.e, to all classes) or selectively applied (i.e, to specific classes). As of the current version db4o sets the default update depth to 0 that means that only value types and strings fields will be updated when an object is updated (i.e, reference fields will not be updated). Optionally it's possible to propagate updates to all objects reachable from the object being updated by configuring cascade on update for the specific classes you want to propagate updates.

IConfiguration.ObjectClass(typeof(your_type)).CascadeOnUpdate(true);

When configured this way, db4o will go down, updating all objects reachable from your_type until it reaches an object not configured for cascade updating (note that this may impact performance severely if this object references a lot of objects also configured for cascade updating).

Transparent Activation

Transparent Activation is a functionality that enables developers to handle objects returned by queries as if they were fully activated without incurring the cost of activating a whole graph of objects. In other words, when TA is enabled, developers may code as if activation depth was set to infinity without having to worry about the whole set of objects being loaded into memory. Basically, Transparent Activation transfers the responsibility of object activation from developers to db4o allowing them to focus on their business logic.

Configuring your db4o projects to use TA is 2 steps process:

  • Configure TA support (prior to opening the database):

    IConfiguration.Add(new TransparentActivationSupport());

  • implement IActivatable interface in classes you want to be TA aware.

    You can implement it by yourself or let us (ok, our tools) to do this work for you through Db4oTool (the old Db4oAdmin) application.

Keep in mind that when transparent activation support is enabled IConfiguration.ActivationDepth() is ignored; also, all objects that are note TA aware (i.e, that doesn't implement IActivatable interface) will be fully activated; so, we don't recommend to enable TA if you have lots of objects that doesn't implement this interface. While activating objects db4o will stop following references as soon as it reaches an TA aware object (after all it will be activated when needed).

More technical details about TA can be found here and a full sample along with details about it can be found here.

Putting it all together

Here you can checkout the sample application that shows the points discussed above. It inserts a simple graph of objects into a database and then queries for the root of the objects using different activation depth configurations. Also, you can ask the application to pretend that it's objects supports TA through the -ta command line option.

I recommend you to download it and do the following:

  • Poke around, looking into the code :)
  • Give it a try, i.e, run it without any parameter and see what you get;
  • Try to run it again, but this time passing -ta parameter, and, again, see what you get;
  • Make its classes TA aware (see here the easy way) and try to run it with and without -ta parameter.

If you have any question don't hesitate to ask them here!

Adriano