Managing RavenDB Document Store startup
The RavenDB’s document store is your main access point to the database. It is strongly recommended that you’ll have just a single instance of the document store per each server you are accessing. That usually means that you have to implement a singleton, with all the double checked locking nonsense that is involved in that. I was giving a course in RavenDB last week and I stumbled upon a very nice coding pattern:
public static class Global { private static readonly Lazy<IDocumentStore> theDocStore = new Lazy<IDocumentStore>(()=> { var docStore = new DocumentStore { ConnectionStringName = "RavenDB" }; docStore.Initialize(); //OPTIONAL: //IndexCreation.CreateIndexes(typeof(Global).Assembly, docStore); return docStore; }); public static IDocumentStore DocumentStore { get { return theDocStore.Value; } } }
This is a very readable code, and it handles pretty much all of the treading stuff for you, without obscuring what you really want to do.
And what about when you have multiple servers? How do you handle it then? Same idea, taken one step further:
public static class Global { private readonly static ConcurrentDictionary<string, Lazy<IDocumentStore>> stores = new ConcurrentDictionary<string, Lazy<IDocumentStore>>(); public static IDocumentStore GetDocumentStoreFor(string url) { return stores.GetOrAdd(url, CreateDocumentStore).Value; } private static Lazy<IDocumentStore> CreateDocumentStore(string url) { return new Lazy<IDocumentStore>(() => { var docStore = new DocumentStore { ConnectionStringName = url }; docStore.Initialize(); //OPTIONAL: //IndexCreation.CreateIndexes(typeof(Global).Assembly, docStore); return docStore; }); } }
This is nice, easy and the correct way to handle things.
Comments
In the multiple doc-store scenario, the use of Lazy<> is redundant and pretty much useless.
Once CreateDocumentStore is called you can just return the actual value and put it in the ConcurrentDictionary, no real value in wrapping this in Lazy<>.
Note how you call .Value on it and are forcing evaluation and instantiation immediately after calling CreateDocumentStore anyway.
Itamar, Actually, there is an important reason to want to do that. Imagine that you have two calls to the same document store at the same instant. The Lazy reference make sure that only one call will be made to the initialization function.
Oren, that wouldn't be taken care of by the GetOrAdd of the ConcurrentDictionary?
@njy The documentation for GetOrAdd(TKey, Func<TValue>) says that the "valueFactory may be called multiple times".
However, the ConcurrentDictionary does ensure that only one of the created values will be added to the dictionary and returned. If you created the IDocumentStore immediately you may create multiple of them. By using Lazy<T> though, you create multiple lazily initiated document stores rather than multiple document stores. Only one of the lazy objects will be successful and make it in to the dictionary, so only one of them gets instantiated.
njy:
GetOrAdd is equivalent to: if (!dict.TryGetValue() value = delegate() dict.TryAdd(value) return dict[key]
So no guarantees your delegate will not be invoked if another delegate is already running. As far as I know no method in ConcurrentDictionary is blocking (except maybe in highly contended cases)
Hmm I thought this blog supported at least the markdown for code blocks. Ah well, Chris Chilvers already posted a better reply anyway
sigh It seems I was partly wrong about the blocking part. I just looked at the decompiled sources of ConcurrentDictionary and TryAdd does always use a lock.
@Chris: you're right, thanks
Can we just register it in our favorite IO container as singleton ?
I think I don't like this pattern. It defers the instantiation of the document store to when you actually want to access it. Usually, I want to catch errors early on (raven not available, cannot create indexes, etc.) and handle them gracefully. This becomes especially hard when you don't know exactly / don't see easily where the document store gets created.
This is what I always look at, from Jon Skeet. http://csharpindepth.com/articles/general/singleton.aspx
No, thanks, we have an ioc container for this. No extra code needed at all ;)
// Ryan
@Daniel,
You can just use this class within application_start, and create your indexes there.
Same functionality, with a nicely enclosed factory for your instances.
If you use this in application_start there is no need for all of this - application_start is only run once.
@Radenko of course. This is the practice I've followed for over 2 years in porduction.
@Bill, like AndersM already said - what's the point of using it then?
Nice, but what happened to interesting posts?
What am I missing here? Even with a static class, how could you share the same instance across multiple servers and entirely different app domains?
You are not sharing the instance between different servers. You are sharing several instances of DocumentStores (which point to different servers) in your application.
The post is not very interesting for a web developer who only uses one instance of a documentstore.
AndersM, thanks -- makes sense now.
Paul, This is one application (app domain) accessing different servers.
While a nice way of doing things for a project with external db, it's most certainly not the "correct" way to do it if you have an embedded db.
In this scenario you don't want to delay db initialization to first use, because it creates visible delay to the user. You want to start db init right away in a separate Task, e.g.:
dbLoader = Task.Factory.StartNew<IDocumentStore>(() => BaseViewModel.LoadDocumentStore());
and then have accessor property for your db return dbLoader.Result. Chances are db might not be fully up by the time of first query (so there might still be some delay), but at least you've cut down on waiting.
Why not use a static constructor? They're very simple, and guaranteed by the framework to only be called once.
public static class Global { private static readonly Lazy<IDocumentStore> theDocStore = new Lazy<IDocumentStore>(()=> { var docStore = new DocumentStore { ConnectionStringName = "RavenDB" }; docStore.Initialize();
}
Of course, I meant to remove those lines.
Configurator, There are a few problems with that, in particular, you can only have one of them. For another, it is actually quite hard to predict WHEN it will execute.
I was referring to the single store case. As for predicting when it will execute, it's easy enough to control by using RuntimeHelpers.RunClassConstructor. The only real problem with this sort of thing is exception handling - retrying once the constructor returned is not an option.
Since you're using the ConnectionStringName property shouldn't you hand in a connection string name, not the Url? Or does ConnectionStringName support both? I thought Url would be used in the scenario of handing in the connection strings value.
JT, Actually, the assumption here is that you have a separate connection string defined for each url, and the connection string name is the url.
Comment preview