My receive port is of sqlBinding and typed polling. It invokes a SP to fetch a record and based on filter condition the corresponding orchestration kicks off. The BizTalk group consists of 2 servers; thus 2 ReceiveHostInstances. If both the host instances are running -at some point the same request is being read twice - causing a duplicate at the receivers end. But, why is the reeive port reading it the same record more than once? The proc which reads the updates the record and updates it so that it wont be fecthed again.
I observed this scenario while submitting 10 requests; receive port read 11 times and 11 orchestrations started.
I tried the same (10 request) with one host (as in my Dev), the receive is showing 10 only. Any clues?
The quick answer is that you have two options to fix this problem:
- Fix your stored procedure so that is behaves correctly in concurrent situations.
- Place your SQL polling receive handler within a clustered BizTalk host.
Below is an explanation of what is going on, and under that I give details of implementations to fix the issue:
Explanation
This is due to the way BizTalk receive locations work when running on multiple host instances (that is, that the receive handler for the adapter specified in the receive location is running on a host that has multiple host instances).
In this situation both of the host instances will run their receive handler.
This is usually not a problem - most of the receive adapters can manage this and give you the behaviour you would expect. For example, the file adapter places a lock on files while they are being read, preventing double reads.
The main place where this is a problem is exactly what you are seeing - when a polling SQL receive location is hitting a stored procedure. In this case BizTalk has no option other than to trust the SQL procedure to give the correct results.
It is hard to tell without seeing your procedure, but the way you are querying your records is not guaranteeing unique reads.
Perhaps you have something like:
Select * From Record
Where Status = 'Unread'
Update Record
Set Status = 'Read'
Where Status = 'Unread'
The above procedure can give duplicate records because between the select and the update, another call of the select is able to sneak in and select the records that have not been updated yet.
Implementing a solution
Fixing the procedure
One simple fix to the procedure is to update with a unique id first:
Update Record
Set UpdateId = @@SPID, Status = 'Reading'
Where Status = 'Unread'
Select * From Record
Where UpdateId = @@SPID
And Status = 'Reading'
Update Record
Set Status = 'Read'
Where UpdateId = @@SPID
And Status = 'Reading'
@@SPID should be unique, but if it proves not to be you could use newid()
Using a clustered host
Within the BizTalk server admin console when creating a new host it is possible to specify that that host is clustered. Details on doing this are in this post by Kent Weare.
Essentially you create a host as normal, with host instances on each server, then right click the host and select cluster.
You then create a SQL receive handler for the polling that works under that host and use this handler in your receive location.
A BizTalk clustered host ensures that all items that are members of that host will run on one and only one host instance at a time. This will include your SQL receive location, so you will not have any chance of race conditions when calling your procedure.