The solution also depends partly on how the messages are being handled.
I used a WorkflowService hosted in Windows Server AppFabric with Net.Msmq binding and a transactional queue. The transactional net.msmq binding was needed to handle out of order message processing. The workflow is a .Net 4.0.1 state machine, and messages are coming onto the same queue from different systems. It is possible, for example, to have one system send an update to a state machine instance before another system has sent a message to instantiate it. To enable out of order message processing the workflow service host uses BufferedReceive to lock the messages, and repeatedly retries getting them from the lock subqueue. BufferedReceive has max pending messages set to the maximum likely batch length, because messages in the locked queue are returned to the retry queue at the front.
WF also has a number of throttling settings. My max likely batch length is about 20000. I have set the MaxConcurrentCalls to 64, and the MaxConcurrentInstances to 20000. This results in IIS/WAS handles 64 concurrent calls.
However, and this is the thing, because the Receives in the workflow are one-way, this does not means that the spawned WF processes terminate as soon as the Receive completes. What happens next in my scenario is that after the message is dequeued and a WF instance called, which is one of the 64 calls, a number of following steps are scheduled by the workflow engine and one of those is a database operation.
The gotcha is that 64 calls may be the maximum, but if the rate of message drain is higher than the rate of asynchronous process completion, as the incoming batch of messages are processed there will be a higher number of executing threads (in my case WF instances). This can cause unexpected things to happen, for example the ADO.NET connection pool has a default of 100 as maximum number of connections. This will cause processes to timeout waiting for connections from an exhausted pool. For this particular problem, you could either raise the MaxPoolSize value, or use Service Broker to deal with db operations asynchronously too (which means more complexity in the workflow).
Hope this helps someone.