A Thread per Task Keeps the Headaches Away
Contrary to common advice, with a dedicated thread for each task, debugging couldn't be more simple and straightforward.
Join the DZone community and get the full member experience.Join For Free
One of the very conscious choices we made in RavenDB 4.0 was to use threads. Not just running code that is multi-threaded, but actively use the notion of a dedicated thread per task.
In fact, most of the work in RavenDB (beyond servicing requests) is done by a dedicated thread. Each index, for example, has its own thread, transactions are merged and pushed using (mostly) a single thread. Replication is using dedicated threads, and so does the cluster communication mechanism.
That runs contrary to advice that I have been told many times, that threads are an expensive resource and that we should not hold up a thread if we can use async operations to give it up as soon as possible. And to a certain extent, I still very much believe it. Certainly, all our request processing is using this manner. But as we got faster and faster, we observed some really hard issues with using thread pools, since you can’t rely on them executing a particular request in a given time frame, and having a mixed bag of operations in a thread pool is a mix for slowing the whole thing down.
Dedicated threads give us a lot of control and simplicity, from the ability to control the priority of certain tasks to the ability to debug and blame them properly.
For example, here is how a portion of the threads on a running RavenDB server look in the debugger:
One of our standard endpoints can give you thread timing for all the various tasks we do, so if there is an expensive thing going on, you can tell, and you can act accordingly.
All of this has also led to an architecture that is mostly about independent threads doing their job in isolation, which means that a lot of the backend code in RavenDB doesn’t have to worry about threading concerns. And debugging it is as simple as stepping through it.
That isn’t true for request processing threads, which are heavily async, so they are all doing mostly reads. Even when you are actually sending a write to RavenDB, what is actually happening is that we have the request thread parse the request, then queue it up for the transaction merging thread to actually process it. That means that there is usually little (if any) contention on locks throughout the system.
It is a far simpler model, and it has proven itself capable of making a very complex system understandable and maintainable for us.
Published at DZone with permission of Oren Eini, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.