Database
 sql >> Base de Dados >  >> RDS >> Database

A trava APPEND_ONLY_STORAGE_INSERT_POINT


Continuando minha série de artigos sobre travas, desta vez vou discutir a trava APPEND_ONLY_STORAGE_INSERT_POINT e mostrar como ela pode ser um grande gargalo para cargas de trabalho de atualização pesadas em que qualquer forma de isolamento de instantâneo está sendo usada.

Eu recomendo fortemente que você leia o post inicial da série antes deste, para que você tenha todo o conhecimento geral sobre travas.

O que é a trava APPEND_ONLY_STORAGE_INSERT_POINT?


Para explicar essa trava, preciso explicar um pouco sobre como funciona o isolamento de instantâneo.

Quando você habilita uma das duas formas de controle de versão, o SQL Server usa um mecanismo chamado versão para preservar as versões pré-alterações de um registro no armazenamento de versões em tempdb. Isto se faz do seguinte modo:
  • Um registro é identificado como prestes a ser alterado.
  • O registro atual é copiado no armazenamento de versão.
  • O registro é alterado.
  • Se o registro ainda não tiver uma tag de versão de 14 bytes , um é adicionado ao final do registro. A tag contém um carimbo de data/hora (não em tempo real) e um ponteiro para a versão anterior do registro no armazenamento de versão.
  • Se o registro já tiver uma tag de controle de versão, ele será atualizado com o novo carimbo de data/hora e o ponteiro de armazenamento de versão.

O carimbo de data/hora de controle de versão em toda a instância é incrementado sempre que uma nova instrução ou lote é iniciado, ou uma nova versão de um registro é criada, em qualquer banco de dados em que qualquer forma de isolamento de instantâneo esteja habilitada. Esse carimbo de data/hora é usado para garantir que uma consulta processe a versão correta de um registro.

Por exemplo, imagine um banco de dados que tenha lido o instantâneo confirmado ativado, de modo que cada instrução tenha a garantia de ver os registros no momento em que a instrução foi iniciada. O registro de data e hora de versão é definido para quando a instrução foi iniciada, portanto, qualquer registro encontrado que tenha um carimbo de data e hora mais alto é a versão "errada", e a versão "certa", com um carimbo de data e hora antes do carimbo de data e hora da instrução, deve ser recuperada do loja de versões. A mecânica desse processo não é relevante para os propósitos deste post.

Então, como as versões são armazenadas fisicamente no armazenamento de versões? Todo o registro de pré-alteração, incluindo colunas fora da linha, é copiado no armazenamento de versão, dividido em blocos de 8.000 bytes, que podem abranger duas páginas, se necessário (por exemplo, 2.000 bytes no final de uma página e 6.000 bytes no o início do próximo). Esse armazenamento para fins especiais é composto de unidades de alocação somente anexadas e é usado apenas para operações de armazenamento de versão. É chamado assim porque novos dados só podem ser anexados imediatamente após o final da versão inserida mais recentemente. Uma nova unidade de alocação é criada de vez em quando, e isso permite que a limpeza regular do armazenamento de versões seja muito eficiente, pois uma unidade de alocação desnecessária pode simplesmente ser descartada. Novamente, a mecânica disso está além do escopo deste post.

E agora chegamos à definição do latch:qualquer thread que precise copiar um registro de pré-alteração para o armazenamento de versão precisa saber onde está o ponto de inserção na unidade de alocação somente anexada atual. Essas informações são protegidas pela trava APPEND_ONLY_STORAGE_INSERT_POINT.

Como a trava se torna um gargalo?


Aqui está o problema:há apenas um modo aceitável no qual a trava APPEND_ONLY_STORAGE_INSERT_POINT pode ser adquirida:modo EX (exclusivo). E como você saberá lendo o post de introdução da série, apenas um thread por vez pode segurar a trava no modo EX.

Reunindo todas essas informações:quando um ou mais bancos de dados têm o isolamento de instantâneo ativado e há uma carga de trabalho simultânea alta o suficiente de atualizações para esses bancos de dados, haverá muitas versões sendo geradas pelas várias conexões, e essa trava se tornará um um pouco de gargalo, com o tamanho do gargalo aumentando à medida que a carga de trabalho de atualização aumenta onde o controle de versão está envolvido.

Mostrando o gargalo


Você pode facilmente reproduzir o gargalo para si mesmo. Eu fiz assim:
  • Criou uma tabela com várias colunas inteiras chamadas cXXX, onde XXX é um número e um índice clusterizado em uma coluna de identidade int chamada DocID
  • Inseridos 100.000 registros, com valores aleatórios para todas as colunas
  • Criou um script com um loop infinito para selecionar um DocID aleatório no intervalo de 1 a 10.000, selecionar um nome de coluna aleatório e incrementar o valor da coluna em 1 (daí criando uma versão)
  • Criou nove scripts idênticos, mas cada um selecionando um intervalo de chaves de cluster de 10.000 valores diferentes
  • Defina DELAYED_DURABILITY como FORCED para reduzir as esperas de WRITELOG (é verdade que você raramente faria isso, mas ajuda a agravar o gargalo para fins de demonstração)

Em seguida, executei todos os dez scripts simultaneamente e medi o contador Métodos de acesso:Pesquisas de índice/s para rastrear quantas atualizações estavam ocorrendo. Eu não podia usar Databases:Batch Requests/s, pois cada script tinha apenas um batch (o loop infinito) e não queria usar Transactions/sec, pois ele poderia contar transações internas, bem como aquela que envolve cada atualização.

Quando o isolamento de instantâneo não estava habilitado, no meu laptop Windows 10 executando o SQL Server 2019, eu estava recebendo cerca de 80.000 atualizações por segundo nas dez conexões. Então, quando ativei a configuração READ_COMMMITED_SNAPSHOT para o banco de dados e executei novamente o teste, a taxa de transferência da carga de trabalho caiu para cerca de 60.000 atualizações por segundo (uma queda de 25% na taxa de transferência). Ao analisar as estatísticas de espera, 85% de todas as esperas foram LATCH_EX e, ao analisar as estatísticas de travamento, 100% foram para APPEND_ONLY_STORAGE_INSERT_POINT.

Tenha em mente que eu montei o cenário para mostrar o pior gargalo. Em um ambiente real com uma carga de trabalho mista, a orientação geralmente aceita para uma queda na taxa de transferência ao usar o isolamento de instantâneo é de 10 a 15%.

Resumo


Uma outra área potencial que pode ser afetada por esse gargalo são os secundários legíveis do grupo de disponibilidade. Se uma réplica de banco de dados estiver configurada para ser legível, todas as consultas nela usarão automaticamente o isolamento de instantâneo e todas as repetições de registros de log do primário gerarão versões. Com uma carga de trabalho de atualização alta o suficiente vindo do primário e muitos bancos de dados configurados para serem legíveis, e com o redo paralelo sendo a norma para secundários do grupo de disponibilidade, a trava APPEND_ONLY_STORAGE_INSERT_POINT pode se tornar um gargalo em um secundário legível do grupo de disponibilidade também, o que pode levar à secundário ficando atrás do primário. Eu não testei isso, mas é exatamente o mesmo mecanismo que descrevi acima, então parece provável. Nesse caso, é possível desabilitar o redo paralelo usando o sinalizador de rastreamento 3459, mas isso pode levar a uma taxa de transferência geral pior no secundário.

Deixando de lado o cenário do grupo de disponibilidade, infelizmente, não usar o isolamento de instantâneo é a única maneira de evitar completamente esse gargalo, que não é uma opção viável se sua carga de trabalho depender da semântica fornecida pelo isolamento de instantâneo ou você precisar dele para aliviar problemas de bloqueio (como o isolamento de instantâneo significa que as consultas de leitura não adquirem bloqueios de compartilhamento que bloqueiam as consultas de alteração).

Editar:dos comentários abaixo, você * pode * remover o gargalo da trava usando o ADR no SQL Server 2019, mas o desempenho é muito pior devido à sobrecarga do ADR. O cenário em que a trava se torna um gargalo devido à alta carga de trabalho de atualização não é absolutamente um caso de uso válido para ADR.