We’ve been working a lot on db4o related and db4o based projects lately, and close to completion of the first and most simple product, we really hit a few roadblocks.
UPDATE: Just after releasing this article, I found the bug in our code. It’s not db4o’s fault after all…
One thing up front: We don’t need an object database for the simple tools we currently build, but we felt it was a good idea to get some acquaintance with the technology, because we will certainly need it for our (stealth) startup “pactas” soon. In pactas, the data structure is really complicated (with a fancy class hierarchy) and it probably will change very frequently.
Also, since I am such a big fan of reusability, we really develop a web engine – a framework that allows us to reuse a lot of the code for different projects and make sure most of our code has been tested thoroughly in the field. This proves to be a significant design decision when it comes to data modeling, and while I’m very happy with the decision, it certainly makes development harder.
So, in time with the release of our simple backlink tracker, one of our development database files started to show strange behaviour – a certain query (via LINQ) would not order objects anymore – the
orderby-clause seemingly was completely ignored. Our live server even came up with an “Invalid DateTimeKind specified” exception when trying to perform the query! What’s worse: The problem kept occurring from time to time, but it was not reproducible! Byzantine errors are clearly my favourite…
We thought the issue might be related to the current development/unstable versions of db4o that we were using (7.12 and 7.13). Using the stable version of db4o (7.4) proved difficult, because the old LINQ provider falls back to LINQ to Objects very often, which requires to fetch all objects in question from the database – that is very, very slow for a lot of objects, so we had to abandon that. We clearly wanted to stick to LINQ for a number of reasons (compile-time checking, readability, reusability).
Obviously the problem is related to the
orderby operation on
DateTime fields. I tried to modify the query, removed grouping because I feared it might be unstable (warning: the sort operation is, in fact, unstable! Unstable grouping would be useless, but the grouping is stable so that’s fine), even debugged the db4o code, but I couldn’t find any problem in there. Since the code is rather complex and that was the first time I took a look at it, I was happy to find some of the relevant code at all. Somewhere in the deeps of it, something screwed up. I didn’t want to spend too much time on that since I had to chew some particle physics on the side.
At the time of writing this (in fact, yesterday) I had something like a hotfix, but it turned out it’s completely nonsense – it worked around our internal bug in a very peculiar way. No more, no less.
When talking about this, somebody mentioned that it wasn’t such a good idea to use
DateTime at all, and we should store the ticks instead. That lead to two long discussions with my co-ed Christian. We concluded: First, the power of object databases is that they do not force you to hack around and find different representations for your data (which raises the bar for object databases). The one big shortcoming of SQL is that it forces you to find a second, equally good, but different representation of your data and you need to translate between these representations all the time. You need to synchronize them. And you lose a lot of fancy features (such as lists, generics, inheritance, etc.) on the way.
Secondly, Christian suggested that object databases suffer from leaky abstractions badly, in that they break encapsulation in a way that leaks out a hell of a lot of implementation details. As Joel puts it: “All non-trivial abstractions, to some degree, are leaky.” The point is: With the error we’re currently encountering, it’s becoming a real problem. This is not an imperfect piece of architecture, it’s an exception in a database query. It kills the app dead! Boom!
In order to get activation [depth] straight, db4o needs to know how .NET’s containers work internally – that is clearly a detail that should be hidden, but db4o knows about it. db4o also takes care of that, but it leads to some messy issues. There is special code in db4o that handles containers, non-trivial objects such as
DateTime (which are non-trivial because they use 62 bits for the actual ticks and 2 bits for the DateTimeKind) and Lists. This is an implementation detail of the .NET framework, and it might change over time. There’s been a bug with
Map, and I’m almost sure there is a bug with
DateTime, too. Don’t get me wrong: The fact that some kind of mapping is needed is a somewhat generic (if not the) problem of serialization, it’s not really db4o-specific, and cannot be eliminated. In Hibernate, there is also a lot of code that handles the mapping of lists and the like but it doesn’t rely on implementation detail, thus it’s not (as) leaky.
Back to SQL?
Here’s the thing: I’d be beneficial to have a storage that is independent of the actual implementation on top, because it decouples the data store from the application which is not what db4o does. But wait, that is exactly what SQL is, right? Indeed: SQL forces you to map (or to cut down) your stuff to it’s internal features. A list becomes a foreign key on the other table, but that doesn’t play well with derived types, generics, etc… This is tricky as we all know, and you need lots of code to do that.
Worse, SQL forces you do that mapping for everything, including your own classes, and it demands a schema for every type of object – this is not what I want. I’d like to see a set of base objects in an object database which are natively understood by the DB. These objects can be mapped from and to by a layer which may be part of the db, or can be added manually if you need something very special. Still, it should allow to store your object in the database with all it’s magic, only that known objects will be translated, e.g. a
DateTime will be stored as
long Ticks and a
DateTimeKind flag separated, making comparison operations easier (note that the comparison only works on the ticks: Whether the time is local or UTC is not considered by .NET in comparisons). Lists will be unfolded into an internal tree representation if indexed, so they become much easier to cope with for the database. That would also make managing m:n relations easier, since they could then be viewed as bilateral relationships – something you often need. Right now, they’re unilateral, thereby requiring some additional management on your behalf.
Migrating to SQL is clearly an option for this product, since data couldn’t fit SQL any better, but that doesn’t solve the issue for our pactas project, where we certainly will need schema-less data, complex object hierarchies, etc.
However, there are a few issues that remain unresolved as of now and they do qualify as show stoppers :
- There’s an irreproducible bug that potentially wrecks the db
- There’s a nasty performance issue with larger amounts of data
I’m quite dissatisfied that we have to abandon db4o at this stage, because I believe it’s the best kind of serialization I’ve ever experienced. It’s perfectly simple, it’s fast and most importantly, code-centric. If reusability is a concern, having the database structure and/or the ORM mapper dictate the objects is a major pain.
Entity Framework 4 (EF4) promises to handle this a lot better through “Model First” and “Code Only”, but I am still a bit afraid of EF because it doesn’t appear to be anything near lightweight and we will need schemaless storage for our future products anyways.
Versant, which bought db4o some time ago also offers its large-scale object database, which seems to suit large web-applications a lot better. First, it’s certainly made for huge amounts of data (unlike db4o, which comes from an embedded background and is aimed at database sizes in the low GB area, but supports up to 254 GB per file) and handles multi-threading better. Also, since db4o now moved to v3 of the GPL, it may not be freely usable in non-open source web applications anymore, so both solutions now have a price tag.
There is also an ISV/OEM empowerment program for Versant’s Object Database, which seems to make it affordable, but I haven’t looked at it in detail yet. Over the next weeks, I will have to evaluate a few of those other NoSQL solutions such as Cassandra and MongoDB, just to name two totally different options.
So what was the issue, after all? db4o did not screw up, we did: Take a blend of local and UTC time based on a completely random criterion, add two spoons of daylight savings time changes, add some misconfigured timezone on the server, bake for two weeks at 500 ° C and pling! You got yourself some really strange issue cake. Lessons learned:
- Debugging issue with object databases is harder than with RDBMS because the information is not chopped up.
- Do code reviews. Do code reviews.
- There aren’t too many alternatives to db4o, after all
- With the elaborated architecture and code-centric design we currently have, going back to SQL is a huge pain, and we won’t do it
All serialization is painful.