WCF Client Channel Pool - Improved Client Performance
Not long ago, I posted about WCF client performance and some work I have been doing around improving that with a "Channel Pool" type implementation.
Well its finally ready for some public consumption. You can grab the code here. (http://www.theglavs.com/DownloadItem.aspx?FileID=55)
You can grab the download from here. Its very "apha" at this point and my testing has been limited, but it has been showing consistently beter results than using a standard ClientBase proxy class in WCF.
So first a quick usage example:
public class MyProxy : ClientBasePool<ISomeInterface>, ISomeInterface
{
public void MyInterfaceMethod(string s)
{
Channel.MyInterfaceMethod(s);
}
}
And you use it as you normally would:
MyProxy prox = new MyProxy();
prox.MyInterfaceMethod("Hello");
prox.Close();
And thats it. The same way you would use a normal ClientBase proxy class.
Using this proxy class will typically yield better performance by approx. 20%-50% by optimising the client side of the communication process.
What it Does.
Using the ClientBasePool proxy class will utilise a pool of pre-opened channels behind the scenes. Negotiating the WS-SecureConversation is expensive, so this class manages a pool of channels, that have already done this negotiation before hand, in the background on a separate, low priority thread.
The pool will automatically get refilled in the background (on a separate thread) as channels are removed from the pool. In its default configuration, the pool has a size of 50, and a maximum of 126. The pool is refilled when a "threshold" value is hit. By default this is half the pool size, ie. 25. So when there are 25 or less channels in the pool, the process refills to pool with pre-opened channels.
Additionally, the channels will periodically be checked to see when they were opened, and if they exceed a pre-defined time period . If so, they are closed and removed from the pool. This is also done in the background on a separate thread. Think of this as the Garbage collector process. This is to prevent clients using a channel from the pool that may have been opened half an hour ago, after which the security conversation is not valid (token expired) and the channel is faulted when using it. This process will pro-actively close and remove end-of-life channels, and the refill process will kick in if requied. By default, the channels have a "life" of 90 seconds and are considered end of life after that. The clean up process runs (by default) every 45 seconds to check the pool. This is half of the channels life period.
All these settings are configurable.
My previous post shows some performance tests I initially did. I also did a more recent one using a 20,000 call test for a standard ClientBase proxy, my ClientBasePool proxy, and a comparative test using a single proxy for all calls. The results are:
Normal Proxy (ClientBase): 6:31:647
My Channel Pool ClientPoolBase Proxy: 4:48:618
Single Proxy: 1:5:522
You can see that my ClientBasePool took 4 minutes and 48 seconds compared to a standard proxy time of 6 seconds and 31 seconds. Not huge, but still a significant difference. Obviously, the times are far longer than using a single proxy only (without creating a new proxy for each set of service calls) with a time of 1 minute and 5 seconds.
Caveats
1. I have only tested this using the wsHttpBinding. I think that the netTcp binding wont really need it or wont benefit that much because of increased performance of the protocol. However, it may benefit somewhat, just haven't tried it.
2. Its early days and my time is limited so extensive testing is not possible. If you use it and have an issue, let me know. I'd love to hear about it.
3. During idle times, this channel pool will be periodically closing channels that have expired, and re-opening new ones in preparation for them to be used. Obviously this is extra traffic where there may be none at all.
4. This is NOT faster than using one single proxy class that is held statically for all your service calls. if you can do that, and manage the timeout issues and whatever, then that will easily be the fastest option.
Configuration and Usage
You dont need to set anything explicitly to make this work, but you can change it to better suit your needs.
Namespace: System.ServiceModel.ChannelPool
Main Classes to use (among others):
ClientBasePool : The proxy class to use which interacts with the ChannelPoolFactory and the ChannelPool to get the job done.
ChannelPool : The Channel Pool implementation for each chanel. Normally you should not have to interact directly with this class.
ChannelPoolFactory : Takes care of instantiating and destroying ChannelPool instances for a channel/service interface. To initialise a channel pool and start off the processing threads use:
ChannelPoolFactory<ISomeInterface>.Initialise();
To destroy a channel pool and terminate all background threads, use:
ChannelPoolFactory<ISomeInterface>.Destory();
Configuration Options:
All Configuration options exist in a class called (not surprisingly) Config. I haven't setup reading from a configuration file or anything like that. I'll let the specific implementation take care of that.
The properties in this class of relevance are:
PoolSize : Defaults to 50. The capacity of the channel pool. Max 127. The refill thresold is automatically set to half of the pool capacity, so by default, when 25 channels or less exist in the pool, the refill process is triggered to refill it.
PoolRefillTrigger: Defaults to 25. This value determines how low the pool can get before a refill is triggered. When a new size is set for the PoolSize, then this value automatically gets adjusted to half of the PoolSize.
ChannelLifetime: Defaults to 90 seconds. Channels are closed and removed from the pool after this time.
CleanupInterval: Defaults to half of the ChannelLifetime, ie. 45 seconds. When a new ChannelLifetime period is set, this value is automatically set to approximately half the ChanelLifetime period.
Examples:
System.ServiceModel.ChannelPool.Config.PoolSize = 100;
System.ServiceModel.ChannelPool.Config.ChannelLifetime = 300;
Final Notes:
There is a console application included in the source that I used for some testing. I chopped and changed this many times to try different tests so its a bit of a mess, but not too complex.
I have cleaned up the code somewhat but there are still bits and pieces lying around. I'll get to them. Happy for people to point them out to me though.
Love to hear any feedback.
Remember, grab it from here.