O desempenho de uma tabela na memória é pior do que uma tabela baseada em disco

10

Eu tenho uma tabela no SQL Server 2014 que se parece com o seguinte:

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

com (id1, id2) sendo o PK. Basicamente, id1 é um identificador para agrupar um conjunto de resultados (id2, col1, col2), cujo pk é id2.

Estou tentando usar uma tabela na memória para se livrar de uma tabela existente baseada em disco, que é o meu gargalo.

  • Os dados na tabela são gravados -> lidos -> excluídos uma vez.
  • Cada valor de id1 possui vários (dezenas / centenas de) milhares de id2.
  • Os dados são armazenados na tabela por um período muito curto, por exemplo, 20 segundos.

As consultas realizadas nesta tabela são as seguintes:

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

-- DELETE:
DELETE FROM MyTable WHERE id1 = @value

Aqui está a definição atual que eu usei para a tabela:

CREATE TABLE dbo.SearchItems
(
  [id1] [bigint] NOT NULL,
  [id2] [bigint] NOT NULL,
  [col1] [int] NOT NULL default(0),
  [col2] [int] NOT NULL default(0)

  CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
  INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)

Infelizmente, essa definição resulta em uma degradação do desempenho em relação à situação anterior com uma tabela baseada em disco. A ordem de grandeza é mais ou menos 10% maior (que em alguns casos atinge 100%, então o dobro do tempo).

Acima de tudo, eu esperava obter uma super vantagem em cenários de alta simultaneidade, dada a arquitetura sem bloqueios anunciada pela Microsoft. Em vez disso, os piores desempenhos são exatamente quando há vários usuários simultâneos executando várias consultas na tabela.

Questões:

  • qual é o BUCKET_COUNT correto para definir?
  • que tipo de índice devo usar?
  • por que o desempenho é pior do que com a tabela baseada em disco?

Uma consulta de sys.dm_db_xtp_hash_index_stats retorna:

total_bucket_count = 131072
empty_bucket_count = 0
avg_chain_len = 873
max_chain_length = 1009

Alterei a contagem de buckets para que a saída de sys.dm_db_xtp_hash_index_stats seja:

total_bucket_count = 134217728
empty_bucket_count = 131664087
avg_chain_len = 1
max_chain_length = 3

Ainda assim, os resultados são quase os mesmos, se não piores.

Cristiano Ghersi
fonte
Tem certeza de que não está encontrando parâmetros para farejar? Você já tentou executar as consultas OPTION(OPTIMIZE FOR UNKNOWN)(consulte Dicas de tabela )?
TT.
Meu palpite é que você está enfrentando problemas na cadeia de linhas. Você pode nos dar a saída de select * from sys.dm_db_xtp_hash_index_stats ? Além disso, esta ligação deve responder a maioria / todas as suas perguntas: msdn.microsoft.com/en-us/library/...
Sean Gallardy
4
O índice de hash é útil apenas para predicados nas duas colunas incluídas. Você já tentou sem um índice de hash na tabela?
Mikael Eriksson
Descobri que as melhores melhorias de desempenho com a tecnologia na memória só podem ser alcançadas usando procedimentos armazenados compilados nativamente .
Daniel Hutmacher 19/03/16
@DanielHutmacher FWIW Já vi contra-exemplos em que todo o benefício foi remover os trincos e adicionar procedimentos compilados de forma nativa, resultando em melhorias nulas ou desprezíveis. Eu não acho que exista espaço para uma declaração geral (embora você possa estar certo neste caso, eu nem olhei para os detalhes).
Aaron Bertrand

Respostas:

7

Embora esta postagem não seja uma resposta completa devido à falta de informações, ela poderá direcioná-lo na direção correta ou obter informações que você poderá compartilhar posteriormente com a comunidade.

Infelizmente, essa definição resulta em uma degradação do desempenho em relação à situação anterior com uma tabela baseada em disco. A ordem de grandeza é mais ou menos 10% maior (que em alguns casos atinge 100%, então o dobro do tempo).

Acima de tudo, eu esperava obter uma super vantagem em cenários de alta simultaneidade, dada a arquitetura sem bloqueios anunciada pela Microsoft. Em vez disso, os piores desempenhos são exatamente quando há vários usuários simultâneos executando várias consultas na tabela.

Isso é preocupante, pois definitivamente não deve ser o caso. Certas cargas de trabalho não são encontradas em tabelas de memória (SQL 2014) e algumas cargas de trabalho se prestam a ela. Na maioria das situações, pode haver um aumento mínimo no desempenho apenas migrando e escolhendo os índices adequados.

Originalmente, eu estava pensando muito estreitamente sobre suas perguntas sobre isso:

Questões:

  • qual é o BUCKET_COUNT correto para definir?
  • que tipo de índice devo usar?
  • por que o desempenho é pior do que com a tabela baseada em disco?

Inicialmente, eu acreditava que havia um problema com o real na tabela de memória e os índices não sendo ideais. Embora existam alguns problemas com a definição de índice de hash otimizada para memória, acredito que o problema real esteja nas consultas usadas.

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

Essa inserção deve ser extremamente rápida se envolver apenas a tabela na memória. No entanto, também envolve uma tabela baseada em disco e está sujeita a todos os bloqueios e bloqueios associados a ela. Portanto, o desperdício em tempo real aqui está na tabela baseada em disco.

Quando fiz um teste rápido contra 100.000 linhas de inserção da tabela com base em disco após carregar os dados na memória - foram tempos de resposta em segundos. No entanto, a maioria dos seus dados é mantida apenas por um período muito curto, inferior a 20 segundos. Isso não dá muito tempo para realmente viver em cache. Além disso, não tenho certeza de quão grande AnotherTableé realmente e não sei se os valores estão sendo lidos fora do disco ou não. Temos que confiar em você para obter essas respostas.

Com a consulta Selecionar:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

Novamente, estamos à mercê do desempenho da tabela baseada em interoperabilidade + disco. Além disso, as classificações não são baratas nos índices HASH e um índice não clusterizado deve ser usado. Isso é mencionado no guia Índice que eu vinculei nos comentários.

Para fornecer alguns fatos reais baseados em pesquisas, carreguei a SearchItemstabela na memória com 10 milhões de linhas e AnotherTablecom 100.000, pois não conhecia o tamanho real ou as estatísticas dela. Eu então usei a consulta de seleção acima para executar. Além disso, criei uma sessão de eventos estendida em wait_completed e a coloquei em um buffer de anel. Foi limpo após cada execução. Também corri DBCC DROPCLEANBUFFERSpara simular um ambiente em que todos os dados podem não residir na memória.

Os resultados não foram nada espetaculares quando os observamos no vácuo. Como o laptop em que estou testando está usando um SSD de nível superior, diminuí artificialmente o desempenho baseado em disco da VM que estou usando.

Os resultados chegaram sem informações de espera após 5 execuções da consulta apenas na tabela baseada na memória (removendo a junção e sem subconsultas). Isso é praticamente o esperado.

Ao usar a consulta original, no entanto, tive esperas. Nesse caso, foi PAGEIOLATCH_SH que faz sentido à medida que os dados estão sendo lidos no disco. Como sou o único usuário deste sistema e não gastei tempo para criar um ambiente de teste massivo para inserções, atualizações e exclusões na tabela unida, não esperava que nenhum bloqueio ou bloqueio entre em vigor.

Nesse caso, mais uma vez, a parte significativa do tempo foi gasta na tabela baseada em disco.

Finalmente, a consulta de exclusão. Encontrar as linhas baseadas apenas no ID1 não é extremamente eficiente com um índice has. Embora seja verdade que os predicados de igualdade são os índices de hash adequados, o intervalo no qual os dados se encaixam é baseado em todas as colunas de hash. Assim, id1, id2, em que id1 = 1, id2 = 2 e id1 = 1, id2 = 3 serão hash em diferentes buckets, pois o hash estará entre (1,2) e (1,3). Não será uma varredura simples do intervalo B-Tree, pois os índices de hash não estão estruturados da mesma maneira. Eu esperaria que este não fosse o índice ideal para esta operação, no entanto, não esperaria que ele levasse ordens de magnitude mais longas do que as experimentadas. Eu estaria interessado em ver o wait_info sobre isso.

Acima de tudo, eu esperava obter uma super vantagem em cenários de alta simultaneidade, dada a arquitetura sem bloqueios anunciada pela Microsoft. Em vez disso, os piores desempenhos são exatamente quando há vários usuários simultâneos executando várias consultas na tabela.

Embora seja verdade que os bloqueios são usados ​​para consistência lógica, as operações ainda devem ser atômicas. Isso é feito por meio de um operador de comparação especial baseado em CPU (e é por isso que o In-Memory funciona apenas com determinados processadores [embora quase todos os cpus fabricados nos últimos 4 anos]). Portanto, não temos tudo de graça, ainda haverá tempo para concluir essas operações.

Outro ponto a ser destacado é o fato de que em quase todas as consultas, a interface usada é o T-SQL (e não os SPROCs compilados nativamente), que tocam em pelo menos uma tabela baseada em disco. É por isso que acredito que, no final, não estamos tendo nenhum desempenho aprimorado, pois ainda estamos limitados ao desempenho das tabelas baseadas em disco.

Acompanhamento:

  1. Crie uma sessão de evento estendida para wait_completed e especifique um SPID conhecido por você. Execute a consulta e forneça a saída ou a consuma internamente.

  2. Dê-nos uma atualização sobre a saída do # 1.

  3. Não há um número mágico para determinar a contagem de buckets para índices de hash. Basicamente, desde que os baldes não fiquem completamente cheios e as cadeias de linhas fiquem abaixo de 3 ou 4, o desempenho deve permanecer aceitável. É como perguntar: "O que devo definir no meu arquivo de log?" - vai depender por processo, por banco de dados, por tipo de uso.

Sean Gallardy
fonte