Ayende @ Rahien

Refunds available at head office

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.

Tags:

Posted By: Ayende Rahien

Published at

Originally posted at

Comments

Itamar Syn-Hershko
12/03/2012 10:29 AM by
Itamar Syn-Hershko

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.

Ayende Rahien
12/03/2012 10:31 AM by
Ayende Rahien

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.

njy
12/03/2012 11:54 AM by
njy

Oren, that wouldn't be taken care of by the GetOrAdd of the ConcurrentDictionary?

Chris Chilvers
12/03/2012 12:32 PM by
Chris Chilvers

@njy The documentation for GetOrAdd(TKey, Func) 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 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.

Patrick Huizinga
12/03/2012 12:34 PM by
Patrick Huizinga

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)

Patrick Huizinga
12/03/2012 12:36 PM by
Patrick Huizinga

Hmm I thought this blog supported at least the markdown for code blocks. Ah well, Chris Chilvers already posted a better reply anyway

Patrick Huizinga
12/03/2012 12:48 PM by
Patrick Huizinga

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.

njy
12/03/2012 01:04 PM by
njy

@Chris: you're right, thanks

Radenko Zec
12/03/2012 01:22 PM by
Radenko Zec

Can we just register it in our favorite IO container as singleton ?

Daniel Lang
12/03/2012 02:50 PM by
Daniel Lang

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.

Josh Close
12/03/2012 04:21 PM by
Josh Close

This is what I always look at, from Jon Skeet. http://csharpindepth.com/articles/general/singleton.aspx

Ryan Heath
12/03/2012 04:22 PM by
Ryan Heath

No, thanks, we have an ioc container for this. No extra code needed at all ;)

// Ryan

Bill
12/03/2012 05:47 PM by
Bill

@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.

AndersM
12/03/2012 06:26 PM by
AndersM

If you use this in applicationstart there is no need for all of this - applicationstart is only run once.

Chris Marisic
12/03/2012 07:37 PM by
Chris Marisic

@Radenko of course. This is the practice I've followed for over 2 years in porduction.

Daniel Lang
12/04/2012 02:22 AM by
Daniel Lang

@Bill, like AndersM already said - what's the point of using it then?

Rafal
12/04/2012 06:35 PM by
Rafal

Nice, but what happened to interesting posts?

Paul K
12/04/2012 09:51 PM by
Paul K

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?

AndersM
12/04/2012 10:23 PM by
AndersM

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.

Paul K
12/04/2012 10:25 PM by
Paul K

AndersM, thanks -- makes sense now.

Ayende Rahien
12/04/2012 10:32 PM by
Ayende Rahien

Paul, This is one application (app domain) accessing different servers.

Alexei K
12/05/2012 02:42 PM by
Alexei K

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(() => 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.

configurator
12/06/2012 10:43 PM by
configurator

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 theDocStore = new Lazy(()=> { var docStore = new DocumentStore { ConnectionStringName = "RavenDB" }; docStore.Initialize();

        //OPTIONAL:
        //IndexCreation.CreateIndexes(typeof(Global).Assembly, docStore);

        return docStore;
    });

public static readonly DocumentStore;

Global() {
    DocumentStore = new DocumentStore
        {
            ConnectionStringName = "RavenDB"
        };
    DocumentStore.Initialize();
}

}

configurator
12/06/2012 10:46 PM by
configurator

Of course, I meant to remove those lines.

public static class Global {
    public static readonly DocumentStore;

    Global() {
        DocumentStore = new DocumentStore {
            ConnectionStringName = "RavenDB"
        };
        DocumentStore.Initialize();
    }
}
Ayende Rahien
12/07/2012 05:45 AM by
Ayende Rahien

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.

configurator
12/08/2012 03:06 PM by
configurator

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.

JT
12/10/2012 05:45 PM by
JT

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.

Ayende Rahien
12/11/2012 09:22 AM by
Ayende Rahien

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.

Comments have been closed on this topic.