Eu estava enfrentando alguns problemas de desempenho com uma consulta ontem e, após uma investigação mais aprofundada, notei o que acredito ser um comportamento estranho com um índice columnstore clusterizado do qual estou tentando chegar ao fundo.
A mesa é
CREATE TABLE [dbo].[NetworkVisits](
[SiteId] [int] NOT NULL,
[AccountId] [int] NOT NULL,
[CreationDate] [date] NOT NULL,
[UserHistoryId] [int] NOT NULL
)
com o índice:
CREATE CLUSTERED COLUMNSTORE INDEX [CCI_NetworkVisits]
ON [dbo].[NetworkVisits] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]
Atualmente, a tabela tem 1,3 bilhão de linhas e estamos constantemente inserindo novas linhas nela. Quando digo constantemente, quero dizer o tempo todo. É um fluxo constante de inserção de uma linha de cada vez na tabela.
Insert Into NetworkVisits (SiteId, AccountId, CreationDate, UserHistoryId)
Values (@SiteId, @AccountId, @CreationDate, @UserHistoryId)
Plano de execução aqui
Eu também tenho um trabalho agendado que é executado a cada 4 horas para excluir linhas duplicadas da tabela. A consulta é:
With NetworkVisitsRows
As (Select SiteId, UserHistoryId, Row_Number() Over (Partition By SiteId, UserHistoryId
Order By CreationDate Asc) RowNum
From NetworkVisits
Where CreationDate > GETUTCDATE() - 30)
DELETE
FROM NetworkVisitsRows
WHERE RowNum > 1
Option (MaxDop 48)
O plano de execução foi colado aqui .
Enquanto investigava o problema, notei que a NetworkVisits
tabela tinha aproximadamente 2.000 rowgroups, com cerca de 800 deles em estado aberto e nenhum perto do máximo permitido (1048576). Aqui está uma pequena amostra do que eu estava vendo:
Executei uma reorganização no índice, que comprimiu todos, exceto 1 rowgroup, mas esta manhã verifiquei novamente e novamente temos vários rowgroups abertos - aquele que foi criado ontem após a reorganização, depois 3 outros criados aproximadamente na época da exclusão trabalho correu:
TableName IndexName type_desc state_desc total_rows deleted_rows created_time
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 36754 0 2019-12-18 18:30:54.217
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 172103 0 2019-12-18 20:02:06.547
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 132628 0 2019-12-19 04:03:10.713
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 397718 0 2019-12-19 08:02:13.063
Estou tentando determinar o que possivelmente pode estar causando isso para criar novos rowgroups em vez de usar o existente.
É possivelmente pressão de memória ou contenção entre a inserção e a exclusão? Esse comportamento está documentado em algum lugar?
Estamos executando o SQL Server 2017 CU 16 Enterprise Edition neste servidor.
O INSERT
é MAXDOP 0, o DELETE
é MAXDOP 48. Os únicos rowgroups fechados são os do inicial BULKLOAD
e depois do REORG_FORCED
que eu fiz ontem, então os motivos de trim sys.dm_db_column_store_row_group_physical_stats
são REORG
e NO_TRIM
respectivamente. Não há rowgroups fechados além desses. Não há atualizações sendo executadas nesta tabela. Calculamos em média cerca de 520 execuções por minuto na instrução insert. Não há particionamento na mesa.
Estou ciente de inserções de gotejamento. Fazemos a mesma coisa em outros lugares e não estamos enfrentando o mesmo problema com vários grupos de linhas abertas. Nossa suspeita é que tenha a ver com a exclusão. Cada grupo de linhas recém-criado está próximo ao horário do trabalho de exclusão agendado. Existem apenas dois armazenamentos delta mostrando linhas excluídas. Na verdade, não excluímos muitos dados desta tabela, por exemplo, durante uma execução ontem, ela excluiu 266 linhas.
Com inserções de gotejamento constantes, você pode muito bem acabar com vários rowgroups deltastore abertos. A razão para isso é que quando uma inserção é iniciada, um novo rowgroup é criado se todos os existentes estiverem bloqueados. De Stairway a Columnstore Índices Nível 5: Adicionando Novos Dados a Índices Columnstore
Em geral, o design do índice columnstore é otimizado para inserções em massa e, ao usar inserções trickle, você precisará executar a reorganização periodicamente.
Outra opção, recomendada na documentação da Microsoft, é passar para uma tabela de preparo (heap) e, quando tiver mais de 102.400 linhas, insira essas linhas no índice de columstore. Consulte Índices Columnstore - Diretrizes de carregamento de dados
De qualquer forma, após a exclusão de muitos dados, uma reorganização é recomendada em um índice columnstore para que os dados sejam realmente excluídos e os rowgroups deltastore resultantes sejam limpos.
Existem muitos cenários diferentes que podem causar isso. Vou passar a responder à pergunta genérica em favor de abordar seu cenário específico, que acho que é o que você deseja.
Não é pressão de memória. O SQL Server não solicitará uma concessão de memória ao inserir uma única linha em uma tabela columnstore. Ele sabe que a linha será inserida em um rowgroup delta para que a concessão de memória não seja necessária. É possível obter mais rowgroups delta do que se poderia esperar ao inserir mais de 102.399 linhas por
INSERT
instrução e atingir o tempo limite de concessão de memória fixo de 25 segundos. Esse cenário de pressão de memória é para carregamento em massa, não para carregamento lento.Bloqueios incompatíveis entre
DELETE
eINSERT
é uma explicação plausível para o que você está vendo com sua mesa. Lembre-se de que não faço inserções lentas na produção, mas a implementação de bloqueio atual para excluir linhas de um rowgroup delta parece exigir um bloqueio UIX. Você pode ver isso com uma demonstração simples:Jogue algumas linhas no armazenamento delta na primeira sessão:
Exclua uma linha na segunda sessão, mas não confirme a alteração ainda:
Bloqueios para o
DELETE
porsp_whoisactive
:Insira uma nova linha na primeira sessão:
Confirme as alterações na segunda sessão e verifique
sys.dm_db_column_store_row_group_physical_stats
:Um novo rowgroup foi criado porque a inserção solicita um bloqueio IX no rowgroup que ele altera. Um bloqueio IX não é compatível com um bloqueio UIX. Esta parece ser a implementação interna atual, e talvez a Microsoft a mude com o tempo.
Em termos do que fazer e como corrigi-lo, você deve considerar como esses dados são usados. É importante que os dados sejam o mais compactados possível? Você precisa de uma boa eliminação de rowgroup na
[CreationDate]
coluna? Tudo bem se novos dados não aparecerem na tabela por algumas horas? Os usuários finais prefeririam que as duplicatas nunca aparecessem na tabela em vez de existirem nela por até quatro horas?As respostas para todas essas perguntas determinam o caminho certo para abordar o problema. Aqui estão algumas opções:
Execute um
REORGANIZE
com aCOMPRESS_ALL_ROW_GROUPS = ON
opção no columnstore uma vez por dia. Em média, isso significa que a tabela não excederá um milhão de linhas no armazenamento delta. Essa é uma boa opção se você não precisar da melhor compactação possível, não precisar da melhor eliminação de rowgroup na[CreationDate]
coluna e quiser manter o status quo de excluir linhas duplicadas a cada quatro horas.Divida o
DELETE
em separadoINSERT
eDELETE
declarações. Insira as linhas a serem excluídas em uma tabela temporária como primeira etapa e exclua-TABLOCKX
as na segunda consulta. Isso não precisa estar em uma transação com base em seu padrão de carregamento de dados (somente inserções) e no método que você usa para localizar e remover duplicatas. A exclusão de algumas centenas de linhas deve ser muito rápida, com boa eliminação na[CreationDate]
coluna, o que você obterá com essa abordagem. A vantagem dessa abordagem é que seus rowgroups compactados terão intervalos apertados para[CreationDate]
, supondo que a data dessa coluna seja a data atual. A desvantagem é que suas inserções trickle serão bloqueadas por talvez alguns segundos.Grave novos dados em uma tabela de preparo e libere-os no columnstore a cada X minutos. Como parte do processo de limpeza, você pode pular a inserção de duplicatas, para que a tabela principal nunca contenha duplicatas. A outra vantagem é que você controla a frequência com que os dados são liberados para que você possa obter rowgroups da qualidade desejada. A desvantagem é que novos dados serão atrasados para aparecer na
[dbo].[NetworkVisits]
tabela. Você pode tentar uma visualização que combine as tabelas, mas você deve ter cuidado para que seu processo de liberação de dados resulte em uma visualização consistente dos dados para os usuários finais (você não deseja que as linhas desapareçam ou apareçam duas vezes durante o processo).Por fim, não concordo com outras respostas de que um redesenho da tabela deva ser considerado. Você está inserindo apenas 9 linhas por segundo em média na tabela, o que não é uma taxa alta. Uma única sessão pode fazer 1.500 inserções singleton por segundo em uma tabela columnstore com seis colunas. Você pode querer mudar o design da tabela assim que começar a ver números em torno disso.
Isso parece um caso extremo para os índices de armazenamento de colunas em cluster e, no final, é mais um cenário HTAP sob a consideração atual da Microsoft - o que significa que um NCCI seria uma solução melhor. Sim, imagino que perder a compactação Columnstore no índice clusterizado seria muito ruim em termos de armazenamento, mas se seu armazenamento principal for Delta-Stores, você estará executando não compactado de qualquer maneira.
Também: