I was recently tasked to review the performance of a data intense desktop application that uses WPF 4.5 for front end and streams data from the server using WCF 4.5 services. The application uses Entity Framework 5 as the Object Relational Mapper with SQL 2012. The standard configuration offered by all these technologies don’t usually support scale. While the application in question worked well for use cases where limited users & shorter range of data sets were being requested. But when more realistic test cases were done to simulate traffic from across geographies, with multiple users, for larger data sets – the application went titsup. In this blog post focus I’ll focus on some of the low effort optimizations done to WCF configurations to optimise the application in question.
The golden rule before you start trying to boil the ocean…
“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.”
WCF Binding
If you haven’t been introduced to fiddler yet, I recommend you read up about it & put it to use http://www.telerik.com/fiddler. By adding the below in your application configuration file, you can track all the WCF calls made by your application using fiddler.
<system.net>
<defaultProxy>
<proxy bypassonlocal="False" usesystemdefault="True" proxyaddress="http://127.0.0.1:8888" />
</defaultProxy>
</system.net>
Going through the fiddler capture it didn’t take too long to work out that there were too many calls being made to stream the same data from the server, the volume of data being downloaded from the server was very large. Downloading over 22 million bytes of body once every 10 seconds doesn’t seem correct!
Looking at the configuration of the WCF services in question I saw that the default ‘basicHttpBinding’ was being used. The drawback is that basicHttpBinding in the background uses text/xml encoding. So, each request & response has to go through the overhead of creating an xml envelope, which tends to slow down the response as the message size grows. Check this article on wcf bindings http://msdn.microsoft.com/en-us/magazine/cc163394.aspx & http://www.drdobbs.com/windows/working-with-bindings-in-wcf-45/240164217, a good replacement to reduce this overhead is to use a binding that supports binary encoding. The problem with NetTCP binding is that it is TCP, which may not be suitable for interoperability, Custom binding has the best of both world it allows http based requests using binary encoding http://jeffbarnes.net/blog/post/2007/02/22/wcf-enable-binary-encoding-over-http.aspx. Refer the table below for actual impact on the size of the message upon changing the wcf binding to custom instead of the default httpBinding.
Here is the configuration changes I needed to make to switch from basic to custom binding at both client & server…
Before – basicHttpBinding (Client Binding) | After – CustomBinding (Client Binding) |
<basicHttpBinding> <binding name=”BasicHttpBinding_IxxxService” closeTimeout=”00:10:00″ openTimeout=”00:01:00″ receiveTimeout=”00:10:00″ sendTimeout=”00:10:00″ allowCookies=”false” bypassProxyOnLocal=”false” hostNameComparisonMode=”StrongWildcard” maxBufferSize=”2147483647″ maxBufferPoolSize=”2147483647″ maxReceivedMessageSize=”2147483647″ messageEncoding=”Text” textEncoding=”utf-8″ transferMode=”Buffered” useDefaultWebProxy=”true”> <readerQuotas maxDepth=”128″ maxStringContentLength=”2147483647″ maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxNameTableCharCount=”2147483647″ /> <security mode=”TransportCredentialOnly”> <transport clientCredentialType=”Windows” proxyCredentialType=”None” realm=”” /> <message clientCredentialType=”UserName” algorithmSuite=”Default” /> </security> </binding> </basicHttpBindings> | <bindings> <customBinding> <binding name=”Custom_IxxxService” closeTimeout=”00:10:00″ openTimeout=”00:01:00″ receiveTimeout=”00:10:00″ sendTimeout=”00:10:00″> <binaryMessageEncoding> <readerQuotas maxDepth=”128″ maxStringContentLength=”2147483647″ maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxNameTableCharCount=”2147483647″ /> </binaryMessageEncoding> <httpTransport authenticationScheme=”Negotiate” maxBufferPoolSize=”2147483647″ maxBufferSize=”2147483647″ maxReceivedMessageSize=”2147483647″/> </binding> </customBinding> </bindings> |
Before – basicHttpBinding (Server Binding) | After– CustomBinding (Server Binding) |
<system.serviceModel> <bindings> <basicHttpBinding> <binding maxBufferSize=”2147483647″ maxBufferPoolSize=”2147483647″ maxReceivedMessageSize=”2147483647″ messageEncoding=”Text”> <readerQuotas maxDepth=”128″ maxStringContentLength=”2147483647″ maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxNameTableCharCount=”2147483647″ /> </binding> </basicHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior> <!– To avoid disclosing metadata information, set the value below to false before deployment –> <serviceMetadata httpGetEnabled=”true” /> <!– To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information –> <serviceDebug includeExceptionDetailInFaults=”True” /> <dataContractSerializer maxItemsInObjectGraph=”2147483647″ /> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled=”true” minFreeMemoryPercentageToActivateService=”0″ /> </system.serviceModel> | <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name=”xxxEndPointBehaviour”> <dataContractSerializer maxItemsInObjectGraph=”2147483647″ /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name=”xxxServiceBehaviour”> <serviceMetadata httpGetEnabled=”true” /> <serviceDebug includeExceptionDetailInFaults=”true” /> <dataContractSerializer ignoreExtensionDataObject=”false” maxItemsInObjectGraph=”2147483647″ /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration=”xxxServiceBehaviour” name=”namespace.xxxService”> <endpoint address=”” behaviorConfiguration=”xxxEndPointBehaviour” binding=”customBinding” bindingConfiguration=”Custom_IxxxService” name=”xxxService” bindingName=”Custom_IxxxService” contract=”namespace.IxxxService” /> </service> </services> <bindings> <customBinding> <binding name=”Custom_IxxxService” closeTimeout=”00:10:00″ openTimeout=”00:01:00″ receiveTimeout=”00:10:00″ sendTimeout=”00:01:00″> <binaryMessageEncoding> <readerQuotas maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxDepth=”128″ maxNameTableCharCount=”2147483647″ maxStringContentLength=”2147483647″/> </binaryMessageEncoding> <httpTransport maxBufferPoolSize =”2147483647″ maxBufferSize=”2147483647″ maxReceivedMessageSize=”2147483647″ /> </binding> </customBinding> </bindings> <serviceHostingEnvironment multipleSiteBindingsEnabled=”true” minFreeMemoryPercentageToActivateService=”0″ /> </system.serviceModel> |
IIS Dynamic Compression on HTTP requests
IIS 7.0 and above offers Dynamic Compression on HTTP Web Requests. Inspecting the http request I noticed that the request header has ‘gzip deflate’ however the response is _not_ compressed using either of the 2 algorithms.
I followed the instructions here to configure dynamic compression on IIS http://www.hanselman.com/blog/EnablingDynamicCompressionGzipDeflateForWCFDataFeedsODataAndOtherCustomServicesInIIS7.aspx. Results, amazing!
Results
So, comparing the results…
basic HTTP Binding | binary encoding | IIS Dynamic Compression | |
Response size | 22,466,729 bytes | 15,726,710 bytes | 2,713,986 bytes |
% reduction | – 30% better than basic Http binding | – 82% better than binary encoding – 88% better than http binding |
Summary
In addition to this further improvements were made using guidelines here http://msdn.microsoft.com/en-us/library/vstudio/hh273113(v=vs.100).aspx, however the 2 listed above seemed the simplest, high impact low effort… low hanging fruits…
Cheers,
Tarun :-]