If you were to ask seasoned developers
whether to use a multithreaded approach to respond to a set of requests,
each resulting in a series of calculations followed by the formatting
of a response, or simply resorting to using a limited number of threads
to respond to these requests, they would likely say that a single
threaded approach would be their method of choice. An alternative would
be using a limited pool of prespawned threads to perform these
transactions, and they would warn against the use of a single worker
thread per request. This is because the act of spawning threads and
allocating their proper resources and soft context switching is somewhat
expensive, but mostly because of the complexity associated with
multithreaded design and the uncertainty of the order of execution.
You should also be wary of using Parallel Actions
shapes for the same reasons. There are hidden costs associated with
using Parallel Actions shapes. The engine decides whether new threads
need to be allocated to perform the parallel branches and implements a
persistence point before the parallel action, then one at the ending
synchronization point. Figures 1, 2, and 3
illustrate three different ways to perform the same action and
highlight that using parallel actions would result in the worst
performance. There is also the risk of corrupting data, as interleaved
data access from multiple parallel branches might lead to unexpected
behavior and undesirable values. To avoid data corruption, the logic
accessing data should be encapsulated within synchronized scopes.
Synchronized scopes will ensure that the data is being accessed by one
thread or branch at a time. Using synchronized scopes will result in
parallel branches being blocked on each other to ensure data integrity.
This will slow down execution to ensure the predictability of the
outcome. Depending on how complex and interdependent the logic is, it
might be simpler to serially perform the data access instead of using
parallel actions.
The use of parallel branches in orchestrations does
not mean that these branches will run on parallel threads. It is simply
an indication to the XLANG engine that operations in these branches may
be interleaved if necessary! The engine then makes the decision to run
them on separate threads or interleave them. For example, if you place a
Terminate shape inside a Parallel Actions shape, and the branch with
the Terminate shape on it is run, the instance completes immediately,
regardless of whether other branches have finished running. Depending on
your design, results might be unpredictable in this case.
|
|
One of the authors of Pro BizTalk 2006,
our fellow architect Ahmed Metwally, once got a call from a developer
who was seeing his BizTalk host instance recycle every time he sent more
than five concurrent messages to his solution. The problem was
reproducible and easy to diagnose, as the error message in the event log
was "not enough threads" to create new orchestration instances.
Although BizTalk dehydrates orchestration instances to manage its
resources and the number of active threads versus the number of
available threads in the thread pool, if all current running instances
are active, BizTalk may not be able to dehydrate them to free up some
resources.
In the scenario we're talking about, the developer
had an orchestration subscribed to a receive port and upon activating a
receive would issue five calls to an external managed .NET assembly on
five parallel branches in a Parallel Actions shape. The external
assembly performed a set of calculations and eventually called a web
service to perform a transaction. The test was being implemented on the developer's machine that luckily was set to have a pool of 25 threads max per CPU. Had the settings been higher, it would have taken longer for the developer to find the problem.
We are sure that by now you are aware of the point we
are trying to make. Although calling a web service from within an
expression instead of using the messaging subsystem would not be
considered wise, making the calls in parallel from within Parallel
Actions shapes results in the exhaustion of available threads in the
thread pool and prevents the host from handling new instances. BizTalk
uses the default thread pool provided by the .NET common language
runtime (CLR); tuning the thread pool is simply a matter of setting the
proper values in the BizTalk Server 2009 Administration Console for the
particular BizTalk host. With automatic throttling in BizTalk 2009, you
will not have to worry as much about their servers recycling, but tuning
the server with the proper values for the thread pool based on the
projected and modeled loads will lead to an optimal application
performance.
In short, BizTalk application performance
can be easily optimized by monitoring and tuning the minimum and maximum
number of threads in the thread pool and the number of in-flight
messages; monitoring the number of persistence points and their effect
on the overall performance; and tweaking orchestrations to minimize the
number of persistence points.