nservicebusnservicebus-distributor

NServiceBus stops a message from being handled by two consumers?


The NServiceBus documentation lists a benefit of SQL transport as:

Queues support competing consumers (multiple instances of same endpoint feeding off of same queue) so there is no need for distributor in order to scale out the processing

http://docs.particular.net/nservicebus/sqlserver/design

Who does NServiceBus prevent a message from being handled by multiple consumers if multiple consumers have subscribed to the same queue?

Does NServiceBus lock the entire table until the message is handled? Or is the message marked as 'being processed' ??


Solution

  • The SQL Transport uses very specific lock hints in order to lock a row and cause other competing threads to ignore any row currently locked.

    From NServiceBus.SqlServer 2.2.0 (current version at the time I'm writing this) the SQL used, but reformatted by me, is:

    WITH message AS 
    (
        SELECT TOP(1) * 
        FROM [{Schema}].[{Queue}] WITH (UPDLOCK, READPAST, ROWLOCK) 
        ORDER BY [RowVersion] ASC
    ) 
    DELETE FROM message 
    OUTPUT deleted.Id, deleted.CorrelationId, deleted.ReplyToAddress, 
           deleted.Recoverable, deleted.Expires, deleted.Headers, deleted.Body;
    

    It uses a Common Table Expression to limit the source data to the one row to return, then uses the following lock hints:

    By executing the entire thing as a delete but then outputting the data about to be deleted, we can read the data, and if the transaction commits, the row is removed. Otherwise, if the transaction rolls back, then the lock is released and the row waits to be picked up by the next competing consumer.