Quando fazemos uso do Message Queue em serviços WCF, a ideia é que as mensagens sejam enfileiradas tanto no cliente quanto no serviço, respeitando as tentativas de entrega e de (re)processamento de cada uma delas. Como uma das principais características é permitir o trabalho desconectado e rodando em horários diferentes, podendo haver um acúmulo de mensagens, o que fará com que o serviço seja estressado quando ele precisar processar cada uma delas, já que ele criará o número de instâncias e permitirá o acesso concorrente de acordo com as configurações do serviço.
Dependendo do que o serviço faz, há a necessidade de envolvê-lo em uma transação para proteger o processamento da mensagem e garantir que se algum problema acontecer, a mensagem seja devolvida para a fila e ser reprocessada mais tarde, mas isso somente acontecerá se as configurações de tentativas estejam devidamente configuradas.
As transações levam o nosso serviço para um outro nível, fazendo com que problemas intermitentes “não afetem” o processamento da mensagem, ou seja, se o banco de dados estiver indisponível por algum motivo, talvez pouco tempo depois já será possível acessá-lo sem problemas. Utilizar as tentativas de reprocessamento aqui, irá auxiliar a garantir esses ciclos, devolvendo a mensagem para a fila e contabilizando o número de tentativas.
Apesar da transação garantir essa confiabilidade, ela tem um efeito colateral que afeta a performance do processamento, pois há um overhead associado ao uso dela. Uma otimização que o WCF nos permite é agrupar o processamento de várias mensagens em um único batch, ou seja, uma transação é criada protegendo o processamento de várias mensagens que rodarão dentro dela, efetivando (commit) todas elas em uma única unidade, ou seja, se uma das mensagens falhar ao ser processada, todas as outras serão desfeitas, garantindo a atomicidade de todas as mensagens envolvidas na transação.
Para controlar isso, que por padrão está desabilitado, temos um behavior em nível de endpoint chamado de TransactedBatchingBehavior, que possui uma única propriedade chamada de MaxBatchSize, que recebe um inteiro que corresponde a quantidade de mensagens que serão agrupadas dentro de uma mesma transação. O exemplo abaixo ilustra a configuração deste behavior, configurando a quantidade do batch como 30.
<system.serviceModel>
<services>
<service name=”ServicoDeGestaoDePedidos”
behaviorConfiguration=”serviceConfig”>
<endpoint address=”net.msmq://localhost/private/App/ServicoDeGestaoDePedidos.svc”
behaviorConfiguration=”endpointBehavior”
binding=”netMsmqBinding”
bindingConfiguration=”bindingConfig”
contract=”IGestorDePedidos”/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name=”serviceConfig”>
<serviceMetadata httpGetEnabled=”true”/>
<serviceDebug includeExceptionDetailInFaults=”true”/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name=”endpointBehavior”>
<transactedBatching maxBatchSize=”30″ />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Obviamente que nem todas as chamadas serão colocadas dentro de um mesmo batch, ou seja, somente aquelas que possuem a propriedade TransactionScopeRequired e TransactionAutoComplete do atributo OperationBehaviorAttribute definidas como True. Durante o processamento, o WCF irá extrair as mensagens do Message Queue e colocará dentro de uma mesma transação até que o valor definido no propriedade MaxBatchSize seja atingido. O commit será executado desde que nenhuma falha aconteça e:
-
A quantidade for atingida.
-
Quando encontrar uma mensagem com o atributo TransactionScopeRequired definido como False. O próximo batch somente será criado quando encontrar outra mensagem com TransactionScopeRequired definido como True.
-
Se não existir mais mensagens na fila e a quantidade ainda não foi atingida.
-
Depois de 80% do tempo de timeout da transação.
Conclusão: Utilizar essa configuração garante uma melhor performance, evitando o overhead da criação de uma transação para proteger cada mensagem, mas é importante dizer que se uma mensagem estiver danificada, pode evitar o processamento das outras que estejam íntegras, independentemente se elas estejam ou não relacionadas.