No meu post anterior, discuti as esperas LCK_M_XX, ASYNC_NETWORK_IO e OLEDB e as reações instintivas a elas. Neste post vou continuar com o tema de estatísticas de espera e discutir a espera SOS_SCHEDULER_YIELD.
Quando o SOS_SCHEDULER_YIELD é o mais prevalente em um servidor, é comum ver o uso sustentado e alto da CPU. A reação instintiva aqui é que o servidor deve estar sob pressão da CPU, ou que um spinlock é o problema.
Precisamos de um pouco de fundo aqui para entender essas duas reações.
Agendamento de thread
O agendamento de threads no SQL Server é gerenciado pelo próprio SQL Server, não pelo Windows (ou seja, não é preemptivo). A parte do SO SQL do Mecanismo de Armazenamento fornece funcionalidade de agendamento e transição de threads de execução em um Processador (onde o estado do thread é RUNNING) para estar na Waiter List aguardando que um recurso fique disponível (o estado é SUSPENDED) para estar no Runnable Fila assim que o recurso estiver disponível (estado é RUNNABLE) esperando para chegar ao topo da fila e voltar ao Processador novamente (de volta ao estado sendo RUNNING). Coloquei em maiúscula Processor, Waiter List e Runnable Queue para identificá-los como partes de um agendador.
Sempre que um thread precisa de um recurso que não pode adquirir imediatamente, ele é suspenso e aguarda na Waiter List para ser informado (sinalizado) de que seu recurso está disponível. O tempo gasto na Waiter List é o tempo de espera do recurso e o tempo gasto na Runnable Queue é o tempo de espera do sinal. Juntos, eles se combinam para ser o tempo de espera total. O SQL OS acompanha o tempo de espera e o tempo de espera do sinal, portanto, precisamos fazer algumas contas na saída de sys.dm_os_wait_stats para derivar o tempo de espera do recurso (veja meu script aqui).
A Waiter List não é ordenada (qualquer thread nela pode ser sinalizada a qualquer momento e passar para a Runnable Queue) e a Runnable Queue é First-In-First-Out (FIFO) quase 100% do tempo. A única exceção à Fila Executável sendo FIFO é onde vários grupos de carga de trabalho do Administrador de Recursos foram configurados no mesmo pool de recursos e têm prioridades diferentes entre si. Eu nunca vi isso usado com sucesso na produção, então não vou discutir mais.
Há outra razão pela qual um thread pode precisar sair do Processador – ele esgota seu quantum. O quantum de thread no SQL OS é fixado em 4 milissegundos. A própria thread é responsável por determinar que seu quantum foi esgotado (chamando rotinas auxiliares no SQL OS) e desistindo voluntariamente do processador (conhecido como yield). Quando isso ocorre, o encadeamento se move diretamente para a parte inferior da Fila Executável, pois não há nada para esperar. O SQL OS deve registrar um tipo de espera para essa transição fora do Processador e registra SOS_SCHEDULER_YIELD.
Esse comportamento é muitas vezes confundido com a pressão da CPU, mas não é – é apenas o uso sustentado da CPU. A pressão da CPU, e reconhecê-la, é outro tópico para um post futuro. No que diz respeito a este post, desde que o tempo médio de espera do sinal seja baixo (0-0,1-0,2ms), é uma aposta bastante segura que a pressão da CPU não é um problema.
Spinlocks
Um spinlock é uma primitiva de sincronização de nível muito baixo que é usada para fornecer acesso thread-safe a estruturas de dados no SQL Server que são extremamente quentes (muito voláteis e acessadas e alteradas com frequência incrível por vários threads). Exemplos de tais estruturas são a lista sem buffer em cada parte do buffer pool e a matriz de pesos de preenchimento proporcional para os arquivos de dados em um grupo de arquivos.
Quando um thread precisa adquirir um spinlock, ele verifica se o spinlock está livre e, em caso afirmativo, o adquire imediatamente (usando uma primitiva de linguagem assembly interligada como 'test bit clear and set'). Se o spinlock não puder ser adquirido, o thread imediatamente tenta adquiri-lo novamente, e novamente, e novamente, por até mil iterações, até que ele recue (dorme um pouco). Isso não é registrado como nenhum tipo de espera, pois o encadeamento simplesmente chama a função sleep() do Windows, mas pode fazer com que outros encadeamentos que estão esperando tenham tempos de espera de sinal grandes (10-20ms+) enquanto o encadeamento adormecido permanece no processador até que ele recebe o spinlock.
Por que estou falando de spinlocks? Porque eles também podem ser uma causa de alto uso da CPU, e há um equívoco de que os spinlocks são uma causa de esperas SOS_SCHEDULER_YIELD. Eles não são.
Causas SOS_SCHEDULER_YIELD
Portanto, há uma causa para o SOS_SCHEDULER_YIELD:um encadeamento esgotando seu quantum de agendamento e instâncias altamente recorrentes pode fazer com que SOS_SCHEDULER_YIELD seja a espera mais prevalente junto com o alto uso da CPU.
Você não verá as esperas SOS_SCHEDULER_YIELD aparecerem na saída de sys.dm_os_waiting_tasks, pois o encadeamento não está aguardando. Você pode ver qual consulta está gerando as esperas SOS_SCHEDULER_YIELD consultando sys.dm_exec_requests e filtrando na coluna last_wait_type.
Isso também significa que quando você vir SOS_SCHEDULER_YIELD na saída de sys.dm_os_wait_stats, a espera do recurso será zero, porque na verdade não esperou. Mas lembre-se de que cada uma dessas 'esperas' equivale a 4 ms de tempo de CPU acumulado para a consulta.
A única maneira de provar o que está causando as esperas SOS_SCHEDULER_YIELD é capturar as pilhas de chamadas do SQL Server quando esse tipo de espera ocorre, usando eventos estendidos e símbolos de depuração da Microsoft. Eu tenho uma postagem no blog que descreve e mostra como realizar essa investigação, e há um ótimo whitepaper sobre spinlocks e investigações de spinlock que vale a pena ler se você estiver interessado nessa profundidade de detalhes internos.
Para o caso de exaustão quântica, essa não é a causa raiz. É mais um sintoma. Agora precisamos considerar por que uma thread pode estar esgotando seu quantum repetidamente.
Um thread só pode esgotar seu quantum quando pode continuar processando o código do SQL Server por 4ms sem precisar de um recurso que outro thread possua - sem espera por bloqueios, travas de página, páginas de arquivos de dados a serem lidas do disco, alocações de memória, crescimento de arquivos, registro em log , ou a infinidade de outros recursos que um thread pode precisar.
A parte de código mais comum em que a exaustão quântica pode ocorrer e acumular grandes quantidades de esperas SOS_SCHEDULER_YIELD é a varredura de um índice/tabela onde todas as páginas de arquivos de dados necessários estão na memória e não há contenção para acesso a essas páginas, e é isso que Recomendo que você procure nos planos de consulta quando vir SOS_SCHEDULER_YIELD como o tipo de espera superior – varreduras de índice/tabela grandes e/ou repetidas.
Isso não significa que estou dizendo que varreduras grandes são ruins, pois pode ser que a maneira mais eficiente de processar sua carga de trabalho seja por meio de uma varredura. No entanto, se as esperas SOS_SCHEDULER_YIELD forem novas e incomuns e forem causadas por varreduras grandes, você deverá investigar por que os planos de consulta estão usando varreduras. Talvez alguém tenha descartado um índice não clusterizado crítico ou as estatísticas estejam desatualizadas e, portanto, um plano de consulta incorreto foi escolhido, ou talvez um valor de parâmetro incomum tenha sido passado para um procedimento armazenado e o plano de consulta tenha solicitado uma verificação ou uma alteração de código ocorreu sem suporte a adições de índice.
Resumo
Assim como com outros tipos de espera, entender exatamente o que SOS_SCHEDULER_YIELD significa é a chave para entender como solucioná-lo e se o comportamento é esperado devido à carga de trabalho que está sendo processada.
No que diz respeito às estatísticas gerais de espera, você pode encontrar mais informações sobre como usá-las para solucionar problemas de desempenho em:
- Minha série de postagens do blog SQLskills, começando com as estatísticas de espera, ou diga-me onde dói
- Minha biblioteca de tipos de espera e classes de trava aqui
- Meu curso de treinamento on-line Pluralsight SQL Server:solução de problemas de desempenho usando estatísticas de espera
- Consultor de desempenho do SQL Sentry
No próximo artigo da série, discutirei outro tipo de espera que é uma causa comum de reações instintivas. Até então, feliz solução de problemas!