This post is the story of RavenDB-6230, or as it is more commonly known, “creating auto-index on non-existent field breaks querying via ID.” It isn’t a big or important bug and it has very little real world impact — but it is an interesting story because it shows one of the hardest things that we deal with: not an issue with a specific feature, but the behavior of the system as a whole, especially when we have multiple things that may affect the end result.
Let's see what the bug actually is. We have the following query:
This works and gets you the right results. Under the cover, it will select the appropriate index (and create one if it isn’t there) to query and get the right results.
Now, issue the following query on a nonexistent field:
Issue the first query again. You’ll get no results.
That is a bit surprising, I’ll admit, but it makes absolute sense when you break it up into component parts.
First, a query by ID can be answered by any index, but if you don’t have an index, one will be created for you. In this case, it will be on the
__document_id field since we explicitly queried on that. This isn’t typically done explicitly, which is important to understand the bug.
Then, we have another query on another field, so we generate an index on that, as well. We do that while taking into account the historical behavior of the queries on the server. However, we ignore the
__document_id field because all indexes already contain it, so it is superfluous. That means that we have an index with one explicit field (
Nice_Doggy_Nice, in this case) and an implicit one (
__document_id). This works great. Even though in one case it is implicit and the other explicit, there is no actual difference in how we treat them.
So, what is the problem? The problem is that the field
Nice_Doggy_Nice doesn’t actually exist. When it comes time to actually index documents using this index, we read the document and index that, but find that we have nothing to index. At this point, we have only a single field to index (just the document ID) but as it is an implicit field and we have nothing else, we skip indexing that document entirely. The example I used in the office is that you can’t get an answer for “when was the last time you had given birth” if you ask a male (except Schwarzenegger, in which case the answer is 1994).
So far, it all makes sense. However, we need to introduce another feature into the mix: the RavenDB query optimizer.
That component is responsible for routing dynamic queries to the most relevant index, and it is doing that with the idea that we should direct work to the biggest index around because that would make it the most active, at which point we can retire the smaller indexes (which are superfluous once the new wider index is up to date).
All features are working as intended so far. The problem is that the query optimizer indeed selects the wider index, and it is an index that has filtered all the results, so the query by ID returns nothing.
Everything works as designed, and yet the user is surprised.
In practice, there are several mitigating factors here. The only way you can get this issue is if you never made any queries on any other (valid) fields on the documents in question. If you have made even a single such query, you’ll not be able to reproduce it. So, you have to really work hard at it to get it to fail. However, the point isn’t so much the actual bug, but pointing how multiple unrelated behaviors can combine to cause a bit of a problem.
As an end note, I highly recommended reading How Complex Systems Fail by Richard I. Cook.