Estamos tentando fazer com que o Service Broker trabalhe em nosso ambiente para resolver um caso de negócios. Não sei se o título da mensagem é bom, mas minha dúvida está abaixo. Mas pode não ser uma boa pergunta, então depois disso é o que estamos fazendo e porque eu acho que é a pergunta certa.
Quantas mensagens devem ser enviadas em uma conversa antes de encerrar a conversa?
Queremos usar o Service Broker para atualizar de forma assíncrona uma tabela de resultados. A tabela de resultados é achatada e rápida. Temos triggers nas tabelas base que enviam uma mensagem com sua tabela e chave primária. Temos três filas:
- Baixa latência - o objetivo é de 15 segundos para processar. Ele lida com itens que mudam em relação a um item específico.
- Fila em massa - o objetivo é de 5 minutos para processar. Ele lida quando algo muda que afeta muitas centenas (ou milhares) de itens. Ele divide a lista de itens que foram afetados e os alimenta na Fila de baixa latência adiada.
- Baixa latência diferida - o objetivo é de 30 minutos para processar. Isso processa itens, mas apenas da fila em massa.
Basicamente, se as informações de um cliente forem atualizadas, isso afetará muitos produtos, então isso será enviado para a fila em massa para um processamento mais lento. No entanto, se um produto for atualizado, ele será enviado para a fila de baixa latência.
Reutilizamos conversas semelhantes ao blog de Remus Rusanu http://rusanu.com/2007/04/25/reusing-conversations/ , com a exceção de que fazemos isso com base no módulo da chave primária. Isso tem o benefício colateral de ajudar na desduplicação de chaves primárias.
Então, estamos reutilizando as conversas e estamos dentro das nossas diretrizes. Com dois threads, consegui gravar 125 mensagens/segundo (queda artificial de vários milhares de mensagens), o que é mais do que capaz de acompanhar a produção (est. 15 mensagens/s).
No entanto, o problema que estamos enfrentando é que, após um período de tempo, ~4 horas ou 120 mil mensagens, começamos a ver blocos e alta contenção no sysdesend e na tabela de filas. As fechaduras são LCK_M_U e são fechaduras KEY. Às vezes, o hobt resolve para sysdesend e outras vezes para a tabela de fila específica (queue_).
Temos um processo em vigor que encerrará as conversas após 24 horas ou 30 minutos de inatividade, podemos apenas aumentar o tempo antes de alternar entre as conversas.
Estamos usando o SQL 2016 Enterprise (13.0.4001.0)
- Disparar Incêndios (enviar para baixa latência ou em massa)
- Procure ou crie um identificador de conversa.
- Enviar mensagem
- Procedimento ativado em fila
- Atualizar tabela de resultados
O processo de limpeza é executado a cada 10 minutos para ver se há conversas ociosas. ltd os encontra mais de três vezes seguidas, marca-os como inativos e encerra as conversas.
Por favor, deixe-me saber se há quaisquer detalhes adicionais que possam ser benéficos. Não tenho muita experiência com Service Broker, então não sei se nossas mensagens/s são baixas, altas ou indiferentes.
ATUALIZAR
Então, tentamos novamente hoje e encontramos o mesmo problema. Alteramos a vida útil da conversa para 2 horas e isso não teve efeito. Então, implementamos o truque 150, que apresentava o mesmo problema.
Toneladas de espera em SEND CONVERSATION, aguardando sysdesend. Alguém tem mais alguma ideia?
ATUALIZAÇÃO 2
Executamos o teste por mais tempo hoje e, em um dos períodos de amostra de 17 minutos, processamos 41 mil mensagens em 4 identificadores de conversa. Conseguimos acompanhar, exceto no final, quando os bloqueios no sysdesend e na tabela de filas se tornaram demais, e começamos a ficar para trás antes de pará-lo. Parece que não temos problemas para processar mensagens, sem que as coisas entrem na fila: podemos retirá-las e processá-las pelo menos 5x essa velocidade. Nossa velocidade parece ser limitada com base na adição de mensagens.
Em um teste posterior, removemos um dos gatilhos que representavam 80% das mensagens. Mesmo com essa carga muito reduzida, começamos a ver as mesmas esperas.
ATUALIZAÇÃO 3
Obrigado, Remus, pelo seu conselho (e obrigado por postar artigos tão excelentes no blog sobre o assunto, eles foram fundamentais para chegar a este ponto).
Corremos de novo hoje e nos saímos melhor (já que passamos mais tempo antes de ver as esperas e ainda mais antes de nos aleijar). Então, os detalhes.
Nós mudamos:
Aumentou o número de conversas mantidas por thread de 1:1 para 2:1. Basicamente, tínhamos 8 identificadores de conversa para 4 threads.
consolidou a fila em massa (porque uma mensagem de entrada pode significar centenas de mensagens de saída) para consolidar em menos mensagens maiores.
Observações sobre esta tentativa:
desabilitando o procedimento de ativação da fila de destino. nenhuma alteração no bloqueio (esperamos 5 minutos) e as mensagens foram enviadas para sys.transmission_queues.
monitorando sys.conversation_endpoints. Esse número passou de 0 a 13K muito rapidamente e depois aumentou mais lentamente ao longo do dia, terminando em cerca de 25K após ~ 5 horas. O bloqueio não começou a ocorrer até atingir 16K+/-
Entrei no DAC e executei os comandos DBREINDEX para as filas, embora a partir de uma consulta, os registros fantasmas nunca tenham ficado acima de 200 ou mais antes da limpeza chegar e reduzir a contagem para 0.
sysdesend e sysdercv tinham contagens idênticas de 24.932 quando terminei o teste.
processamos ~310 mil mensagens em 5 horas.
Fomos tão longe antes que as coisas desmoronassem que eu realmente pensei que conseguiríamos desta vez. Amanhã tentaremos forçar as mensagens a passarem pelo fio.
Eu sei que é indelicado responder sua própria pergunta, mas eu queria encerrar isso para quem estiver interessado. Finalmente conseguimos resolver o problema, ou pelo menos resolvê-lo o suficiente para atender aos nossos requisitos. Quero agradecer a todos que contribuíram com comentários; Remus Rusanu e Kin, pois foram muito úteis.
Nosso banco de dados está bastante ocupado e está no modo RCSI. Temos vários (milhares) de dispositivos móveis que atualizam suas informações de localização a cada 45 segundos. Por meio dessas atualizações, várias tabelas obtêm suas informações atualizadas (design ruim, pois eu teria limitado as informações voláteis a uma única tabela e depois as juntaria para obter os resultados). Essas tabelas são as mesmas para as quais estávamos tentando gerar informações de relatório de forma assíncrona, em vez de fazer com que os usuários finais fossem diretamente nas tabelas base.
Inicialmente, tínhamos os gatilhos fazendo um cursor sobre os registros modificados em cada instrução de atualização/inserção (deveria ter uma linha na maioria dos casos) e enviando cada chave primária em uma mensagem para o agente de serviço. Dentro do service broker, especialmente a fila em massa, havia outros cursores que executavam o procedimento upsert para o relatório (uma execução por chave primária).
O que finalmente nos fez trabalhar:
Removemos os cursores e decidimos enviar mensagens maiores. Ainda uma mensagem por transação de usuário por tabela, mas agora enviamos mensagens com mais de uma chave primária.
O processador em massa também envia várias chaves por mensagem, o que reduziu o número de CONVERSAÇÕES DE ENVIO que estavam acontecendo enquanto ele embaralhava as mensagens para a outra fila conforme apropriado.
A tabela mais volátil (nossa tabela de dados do dispositivo móvel) teve seus gatilhos removidos. Atualizamos o procedimento upsert para incluir as chaves estrangeiras apropriadas e agora apenas nos juntamos novamente a essa tabela ao buscar resultados para os usuários. Essa tabela contribuiu facilmente com 80% das mensagens que tivemos que processar em um dia.
Processamos cerca de 1 milhão de mensagens por dia (sem a tabela Mobile) e a grande maioria (99%+) de nossas mensagens são processadas dentro do nosso objetivo. Ainda temos o outlier ocasional, mas dada a natureza rara do que é considerado aceitável.
Fatores contribuintes:
Encontrei um bug no procedimento de limpeza de conversa mencionado anteriormente que não estava realmente limpando as conversas adequadamente e encerrando-as prematuramente. Isso agora resultou em nossa contagem de sysdesends para nunca ser mais do que alguns milhares (a maior parte vem do uso do truque de 150).
Os cursores nos gatilhos pareciam ter mais travamento do que o previsto (mesmo com estático, somente para frente). removê-los parece ter tornado os bloqueios que vemos em SEND CONVERSATION mais transitórios por natureza (ou pelo menos as vezes que vemos são muito menores).
Estávamos essencialmente executando duas soluções lado a lado (o back-end da solução Service Broker (para testar sob carga de produção)) e a solução atual (consulta terrível que abrange muitas tabelas).
Como um benefício colateral, isso descobriu um problema de limpeza de registro fantasma e, embora não estivesse nas tabelas do Service Broker (sistema ou fila), é bastante desenfreado em nosso sistema e os sintomas se alinham muito bem com nossa "sem causa clara" problemas que experimentamos às vezes. A investigação está em andamento sobre isso, estamos tentando encontrar as tabelas que estão contribuindo para isso e provavelmente reconstruiremos seus índices rotineiramente.
Agradeço novamente.
*** ATUALIZAÇÃO 2021 ***
Eu tenho outro upvote sobre isso, então eu queria voltar e revisitar. Acabamos abandonando o Service Broker logo depois que fiz essa pergunta, pois nossa carga de trabalho de transações ainda era muito alta/rápida para funcionar com eficiência. Eventualmente, mudamos para esse estilo de fila para nosso sistema de mensagens de alto rendimento e funciona muito bem.
http://kejser.org/implementing-message-queues-in-relational-databases/