Saturday, April 18, 2009

WCF Performance: Making your service run 3 times faster

@YaronNaveh

A lot of people use WCF default settings on production. In many cases changing these defaults can gear up the service throughput dramatically.

Let's look at the following use case:


  • WsHttpBinding is used

  • Message level security is used: X.509 certificate or windows authentication, where client can also use a username/password or be anonymous

  • (Optional assumption) A typical client makes one service call and then disconnects



  • The WsHttpBinding implicit defaults can be explicitly written as bellow:


    <wsHttpBinding>
      <binding name="BadPerformanceBinding">
       <security mode="Message">
        <message clientCredentialType="..."
         negotiateServiceCredential="true"
         establishSecurityContext="true" />
       </security>
      </binding>
    </wsHttpBinding>


    Let's simulate a load on this service by employing many virtual users who constantly call the service one time and immediately disconnect. The number of users should be large enough such that service will use its max capacity. The results are:


    Transactions per second: 15.235
    Average time of a transaction: 1.4 seconds


    Note: I didn't use a super strong server here but as we can see below it shouldn't matter for our needs. Also a load of just a few minutes was enough to prove our theory.






    Those are not very good results of course.

    Now let's tweak the configuration a little bit:


    <wsHttpBinding>
      <binding name="BetterPerformanceBinding">
       <security mode="Message">
        <message clientCredentialType="..."
         negotiateServiceCredential="false"
         establishSecurityContext="false" />
       </security>
      </binding>
    </wsHttpBinding>


    And with the same amount of virtual users we get these results:


    Transactions per second: 51.833
    Average time of a transaction: 0.384 seconds


    That's 3.5 times faster!





    So, what happened here?
    Since the only change we did is in two settings we need to analyze each of them.

    negotiateServiceCredential
    This setting determines whether the clients can get the service credential (e.g. certificate) using negotiation with the service. The credentials are used in order to authenticate the service and to protect (encrypt) the messages. When this setting is set to "true" a bunch of infrastructure soap envelopes are sent on the wire before the client sends its request. When set to "false" the client needs to have the service credentials out of band.

    The trade off here is better performance (using "false") versus more convenience (using "true"). Setting "false" has its hassles as we now need to propagate the service credential to clients. However, performance wise, setting "negotiateServiceCredential" to "false" is always better.

    Take a look at how many infrastructure messages are exchanged when negotiateServiceCredential is "true":



    While when not negotiating life is much brighter:



    establishSecurityContext
    This setting determines whether WS-SecureConversation sessions are established between the client and the server. So what is a secure conversation anyway? In a very simplified manner we can say that a normal secured web service request requires one asymmetric encryption. Respectively, normal N requests require N asymmetric encryptions. Since asymmetric encryption is very slow, setting up a secure conversation is usually a good practice: It requires a one-time asymmetric encrypted message exchange in order to set up a session; Further calls in the session use symmetric encryption which is much faster.

    Now remember that in our case we assume that clients call the service just one time and disconnect. If a secure session is established the message exchange will look like this:


    Message 1: Setting up a secure session (asymmetric encryption)
    Message 2: The actual request (symmetric encryption)


    If we do not use secure session we have:


    Message 1: The actual request (asymmetric encryption)


    So it is clear that we're better off in the latter case.

    With secure sessions there isn't really any trade off and the decision is quite scientific: When only one client request is expected set establishSecurityContext to "false".

    Summary
    Wisely changing WCF defaults can yield a significant improvement in your service performance. The exact changes need to be made and their exact effect are dependent in the scenario. The example above showed how to speed up a certain service 3 times faster.

    @YaronNaveh

    What's next? get this blog rss updates or register for mail updates!

    9 comments:

    Anonymous said...

    How do you think to propagate the credentials from client to server ?

    Yaron Naveh (MVP) said...

    Hi Anonymous

    The scenario I have tested here includes an anonymous client so there's no need to propagate credentials.

    In case there is such a need then these credentials will be passed by the client in each request. Since this is an overhead over using a secured session it will not always outperform it. I assume that for user/pass credentials we will still see a big improvement while for a big SAML token it may not be the best solution.

    Unknown said...

    Hey Yaron, If the client calls the service once in every 10 minutes, do you recommend on setting establishSecurityContext to false?

    Unknown said...

    Hey Yaron, If the client calls the service once on every 10 minutes, do you recommend on setting establishSecurityContext to false?

    Yaron Naveh (MVP) said...

    ronsho

    It depends what is the expected server load.

    If you expect a lot of clients then no point in using these sessions - the number of open sessions is limited, blows memory, and it is fragile since any restart of service or client removes the session.

    If you expect only a few clients then if there is an open session the call to the service will be slightly faster.

    Unknown said...

    How can I set the establishSecurityContext="False" in a customBinding?

    I spent hours but I found just other flag like RequireSecurityContextCancellation but actually they seems to be different stuff.

    I did tried also to put authenticationMode="UserNameForSslNegotiated" instead of "SecureConversation", but I can see again the error message "The security context token is expired or is not valid." so I suppose the SCT is currently used.

    any help?

    Thanks,
    Michael.

    Yaron Naveh (MVP) said...

    Michael

    As long as you do not use "SecureConversation" authentication mode then security context is not used. UserNameForSslNegotiated does not use secure conversation, but it does internally use the ws-trust standard for different purpose, so the error may mention tokens. SslNegotiated maps to the negitiateClientCredential setting.

    You may also want to check http://webservices20.cloudapp.net/

    Carl said...

    I know this is a very old thread however some of us are still using WCF!

    For services that are secured using TRANSPORT security only and using wsHTTPBinding what effect does changing the NegotiateServiceCredential to false have? You say you have to propagate the service credentials to the client however what does that actually mean? Do we have to install the server certificate locally on the clients?

    Thanks

    Yaron Naveh (MVP) said...

    Hi Carl

    If you use Trasnport security only then NegotiateServiceCredential has no effect and its value does not matter. It only matters when message security is used, in which case turning it off will result in a faster communication (for first message) but that means clients need to have the server certificate out of band.