Recycling Dialogs

 

In virtually all the Service Broker articles, books, presentations, and blog samples I have done over the years, one message is sent from the initiator to the target, a response is sent from the target to the initiator and END CONVERSATION is called to terminate the dialog.  While this makes the samples small and easy to understand, it gives the mistaken impression that a dialog is one message in each direction.  One of the most significant advantages of dialogs over other messaging paradigms is that message order is preserved for the entire life of the dialog – even if it lasts for years.  The simple request-response model of communications imposed by RPC and HTTP really doesn’t represent many real-world interactions well.  Take a simple on-line order entry application as an example.  You sign on, add items to the order, view the completed list, check prices, select shipping options, settle on a form of payment, and confirm the order.  There may well be a dozen or more messages involved in the order process and if any of them are lost or processed out of order there’s a good chance that you won’t receive what you expect when the order is delivered.  A dialog does an excellent job of managing this communications exchange that may last for minutes or even days.  Because dialogs are persistent conversations that survive database restarts and even fail-overs and restores, they work well for long-running interactions.  This all means that when you write a Service Broker application, you should carefully consider how dialogs are used and keep them around until you are sure the interaction that is using the dialog is complete.  The in-order processing and the ability to associate state with each conversation make writing asynchronous interactions much easier and more efficient.

 

While keeping a dialog around until you’re done with it is a no-brainer, a more difficult question is should you throw the dialog away (END CONVERSATION) when you’re done or keep it around and use it again.  The Service Broker team spent a great deal of time an effort making dialogs as light-weight as possible but they are not free.  Beginning a dialog involves inserting a row in the conversation endpoints table and probably inserting a row into the conversation groups table.  If you are using dialog security, a session key is generated for the dialog and a security header is added to the first message sent.  When the first message arrives at the target, rows are added to the conversation endpoint and conversation group tables, keys are checked and stored and permissions are checked.  When END CONVERSATION is called, rows are deletes from the conversation endpoint and conversation group tables and a message is sent to the opposite endpoint to let it know that this end of the conversation is going away.  Because Service Broker is part of the database, a lot of this work can be optimized and combined with other changes to minimize the impact on the database so if you use a dialog for exchanging a number of messages, the dialog overhead is negligible but if you are beginning and ending a dialog for every message, the overhead can impact performance.  Another impact of creating millions of dialogs is that at the TARGET endpoint of the conversation, the entry in the endpoint table is kept around in the closed state for about half an hour to prevent replay attacks – someone capturing a message off the wire and sending it again.  If an attacker sends the first message of a dialog a second time, the endpoint must know that the dialog has been terminated so the attacker’s message is rejected.  After half an hour, the message lifetime of all the dialog messages has expired so it’s safe to get rid of the conversation endpoint entry for the dialog.  If you are beginning a new dialog for each message and sending a thousand messages a second, there will be a couple million closed dialog entries in the conversation endpoint table on average. .While this isn’t huge, it does use up some cache memory and a background thread has to clean these up periodically so there is some performance impact.

 

To avoid this overhead, I have worked with quite a few customers to come up with ways to reuse dialogs even though they are sending a stream of unrelated messages.  A typical example is someone who was using Service Broker for asynchronous logging.  He wanted to minimize the Service Broker impact so he just created a single dialog from each system that was sending messages to the logging system.  One dialog handled the load with no difficulty.  He found that moving from a new dialog for each message to sharing the same dialog for all messages improved his performance by about 30%.  He creates a dialog with no lifetime when the system is installed and just uses it forever.  When his application starts he does a query on the sys.conversation_endpoints view to get the dialog handle for the target service.  There’s a possibility that the dialog will experience an error for some reason so his SEND logic catches the invalid dialog messages and begins a new dialog if the old one fails.  I have talked to some other customers about opening several dialogs and using them in a round-robin fashion when a single dialog can’t handle the load but as far as I know, none of them have reached a load that requires this yet.  This will work for applications that run for a long time so they can reuse a pool of dialogs easily.  I’m not sure if it would make sense to recycle a group of dialogs in something like an asynchronous trigger where each transaction would have to find its own dialog handle.  Ultimately, the best way to handle this would probably be to create a dialog pool class that manages a set of dialogs.  When you need a dialog you request one from the pool and then return it when you’re done.  The pool could open additional dialogs when there are outstanding requests.  This kind of pooling will only work for a limited set of scenarios but if your application only sends single messages and runs continuously, this kind of pooling can be a significant performance boost.

 

So why would you ever not do dialog recycling?  Why doesn’t Service Broker just recycle dialogs for you?  There are a few things you have to keep in mind when deciding whether to reuse dialogs or not:

 

·      Dialogs impose order so reusing dialogs imposes an artificial order.  If your message ends up behind a message that takes 10 minutes to process, you will have to wait for your message to get processed even though there may be other threads waiting for messages to process.  If a dialog contains related messages, ordering is a good thing but if messages end up in a dialog because the sender is recycling dialogs, ordering can cause unnecessary delays.  You may decide that an occasional delay is worth it to get the overall throughput improvement offered by dialog recycling but be sure you think through the implications before making the decision.

 

·      Sending a message locks the dialog until the transaction commits so reusing the dialog before the transaction completes can serialize the transactions using the dialog.  This is a significant issue if you are using Service Broker to move part of the transaction processing outside of the transaction scope to improve performance of the sending transactions.  You may end up with a situation where transaction performance actually decreases because they are waiting for locks on a single dialog.  This is a case where a pool of dialogs is a much better solution than a single dialog.  If you leave the Service Broker SEND operation until the very end of the transaction, the dialog lock isn’t held for very long so the serialization effect can be minimized.

 

As with most performance optimizations, premature optimization is generally a bad practice.  Unless you are confident that your application will need to do dialog recycling to meet its performance requirements, you should start by ending dialogs when you are done with them and keep dialog recycling as a performance optimization when you need it.  If you see millions of rows in the sys.conversation_endpoints view, you should start thinking about opportunities for recycling dialogs to reduce this overhead.