Encontre o tamanho não compactado de todas as tabelas em um banco de dados

12

No Dynamics AX, existe um mecanismo de armazenamento em cache no qual as tabelas podem ser configuradas para serem carregadas na memória e armazenadas em cache. Esse cache é limitado a uma certa quantidade de KB para evitar problemas de memória. A configuração da qual estou falando é chamada entiretablecachee carrega a tabela inteira na memória assim que um único registro é solicitado.

Até recentemente, contávamos com alguns scripts para verificar o tamanho das tabelas que possuem essa configuração para ver se o tamanho da tabela está acima desse limite.

Agora, no entanto, a compactação entra em ação e coisas como sp_spaceused ou sys.allocation_units parecem relatar o espaço realmente usado pelos dados compactados.

Obviamente, o servidor de aplicativos está trabalhando com dados não compactados, portanto o tamanho dos dados no disco no SQL Server é irrelevante. Preciso do tamanho real dos dados não compactados.

Eu sei de sp_estimate_data_compression_savings, mas como o nome diz, isso é apenas uma estimativa.
Eu preferiria ter o tamanho o mais correto possível.

A única maneira que eu conseguia pensar era em um SQL dinâmico complicado, criando tabelas não compactadas com a mesma estrutura que as tabelas compactadas, inserindo os dados compactados nessa tabela de sombra e, em seguida, verifique o tamanho dessa tabela de sombra.
Escusado será dizer que isso é um pouco tedioso e leva um tempo para rodar em um banco de dados de várias centenas de GB.

O Powershell poderia ser uma opção, mas eu não gostaria de repetir todas as tabelas para executá select *-las e verificar o tamanho do script, pois isso apenas inundaria o cache e provavelmente levaria muito tempo.

Em resumo, preciso de uma maneira de obter o tamanho de cada tabela, pois ela será descompactada e com a fragmentação da equação apresentada ao aplicativo, se isso for possível. Sou aberto a diferentes abordagens, o T-SQL é o preferido, mas não sou contra o Powershell ou outras abordagens criativas.

Suponha que o buffer no aplicativo seja do tamanho dos dados. Um bigint sempre tem o tamanho de um bigint e um tipo de dados de caractere é de 2 bytes por caractere (unicode). Os dados BLOB também assumem o tamanho dos dados, uma enumeração é basicamente uma int e os dados numéricos são numéricos (38,12), data e hora é o tamanho de uma data e hora. Além disso, não há NULLvalores, eles são armazenados como uma sequência vazia 1900-01-01ou zero.

Não há documentação sobre como isso é implementado, mas as suposições são baseadas em alguns testes e nos scripts usados ​​pelo PFE e pela equipe de suporte (que também ignoram a compactação aparentemente, uma vez que a verificação é incorporada ao aplicativo e o aplicativo não pode dizer). se os dados subjacentes estiverem compactados), que também verificam os tamanhos da tabela. Este link, por exemplo, afirma:

Evite usar caches de tabela inteira para tabelas grandes (no AX 2009, com mais de 128 KB ou 16 páginas, no AX 2012, sobre a configuração do aplicativo 'tamanho da cache da tabela inteira' [padrão: 32 KB ou 4 páginas]) - mova para gravar o cache.

Tom V - tente topanswers.xyz
fonte
3
É hacky, mas talvez uma cópia restaurada com a compactação desativada seja a mais precisa. Então você também está testando restaurações, o que faz você parecer um DBA TOP 1.
Erik Darling
Acredite que essa seria sua melhor aposta. Pode haver maneiras de tentar fazer as contas. Quantas linhas por tipos de coluna definidos e comprimentos multiplicados adicionam os índices, etc. É muito mais trabalhoso do que criar scripts para restaurar e desativar a compactação sugerida por @sp_BlitzErik acima. E quem não gostaria de ser um DBA TOP 1?
Mike Walsh
SUM (datalength ()) para todas as colunas obtém tamanho de dados descompactado?
Tapakah Ua 27/10
@sp_BlitzErik Isso poderia ser uma resposta em vez de um comentário.
Tom V - tentativa topanswers.xyz

Respostas:

7

Preciso do tamanho real dos dados não compactados.
...
eu preferiria ter o tamanho o mais correto possível.

Embora o desejo por essas informações seja certamente compreensível, obtê-las, especialmente no contexto de "o mais correto possível", é mais complicado do que todo mundo espera devido a suposições incorretas. Seja executando a idéia da tabela de sombra descompactada mencionada na pergunta ou a sugestão de @ sp_BlitzErik em um comentário sobre restaurar o banco de dados e descompactar para verificação, não se deve presumir que o tamanho da tabela descompactada == o tamanho dos dados na memória no servidor de aplicativos:

  1. Todas as linhas da tabela estão sendo armazenadas em cache? Ou apenas dentro de um intervalo? A suposição aqui é que é tudo, e isso pode estar correto, mas achei que pelo menos deveria ser mencionado que esse pode não ser o caso (a menos que a documentação indique o contrário, mas esse é um ponto menor, mas não queria) para não ser mencionado).

    A pergunta foi atualizada para indicar: sim, todas as linhas estão sendo armazenadas em cache.

  2. Sobrecarga da estrutura

    1. No lado do banco de dados:
      Página e sobrecarga de linha no lado do banco de dados: quantas linhas cabem em uma página são determinadas por muitos fatores que podem prejudicar as estimativas. Mesmo com um FILLFACTORde 100 (ou 0), ainda é provável que haja algum espaço não utilizado restante na página devido ao fato de não ser suficiente para uma linha inteira. E isso é um acréscimo ao cabeçalho da página. Além disso, se qualquer funcionalidade de isolamento de captura instantânea estiver ativada, acredito que haverá 13 bytes adicionais por linha ocupados pelo número da versão e isso reduzirá as estimativas. Há outras minúcias relacionadas ao tamanho real da linha (bitmap NULL, colunas de comprimento variável, etc.), mas os itens mencionados até agora devem ser o único argumento.
    2. No lado do servidor de aplicativos:
      Que tipo de coleção está sendo usada para armazenar os resultados em cache? Presumo que este é um aplicativo .NET, então é um DataTable? Uma lista genérica? Um SortedDictionary? Cada tipo de coleção tem uma quantidade diferente de ouvida. Eu não esperaria que nenhuma das opções espelhasse necessariamente as despesas gerais de Página e Linha no lado do banco de dados, especialmente em escala (tenho certeza de que uma pequena quantidade de linha pode não ter várias o suficiente para importar, mas você não está procurando diferenças em centenas de bytes ou apenas alguns kB).
  3. Tipos de dados
    1. No lado do banco de dados:
      CHAR/ VARCHARdata é armazenado em 1 byte por caractere (ignorando caracteres de byte duplo no momento). XMLé otimizado para não ocupar tanto espaço quanto a representação de texto implicaria. Esse tipo de dados cria um dicionário de nomes de elementos e atributos e substitui as referências reais a eles no documento por seus respectivos IDs (na verdade, bem legais). Caso contrário, os valores da sequência serão todos UTF-16 (2 ou 4 bytes por "caractere"), assim como NCHAR/ NVARCHAR. DATETIME2está entre 6 e 8 bytes. DECIMALestá entre 5 e 17 bytes (dependendo da precisão).
    2. No lado do servidor de aplicativos:
      Strings (novamente, assumindo .NET) são sempre UTF-16. Não há otimização para cadeias de 8 bits, como o que VARCHARcontém. MAS, as strings também podem ser "internadas", que é uma cópia compartilhada que pode ser referenciada várias vezes (mas não sei se isso funciona para strings em coleções ou, se for, se funciona para todos os tipos de coleções). XMLpode ou não ser armazenado da mesma maneira na memória (vou ter que procurar isso). DateTimeé sempre 8 bytes (como T-SQL DATETIME, mas não como DATE, TIMEou DATETIME2). Decimalé sempre 16 bytes .

Tudo isso a dizer: não há praticamente nada que você possa fazer no lado do banco de dados para obter tamanho de pegada de memória ainda bastante preciso no lado do servidor de aplicativos. Você precisa encontrar uma maneira de interrogar o próprio servidor de aplicativos, depois de ser carregado com uma tabela específica, para saber o tamanho dele. E não tenho certeza se um depurador permitiria ver o tamanho do tempo de execução de uma coleção preenchida. Se não, então a única maneira de chegar perto seria a de passar por todas as linhas de uma tabela, multiplicando cada coluna pela apropriada .NET tamanho (por exemplo, INT= * 4, VARCHAR= DATALENGTH() * 2, NVARCHAR= DATALENGTH(), XML= 🙃, etc), mas que ainda deixa a questão da sobrecarga da coleção mais cada elemento da coleção.

Dada uma nova definição na pergunta, provavelmente seria possível fazer a seguinte consulta para se aproximar. E não importa se a tabela está compactada ou não, embora seja responsabilidade de cada pessoa determinar se a varredura de todas as linhas é apropriada na Produção (talvez seja de uma restauração ou fora do horário de pico):

SELECT
   SUM( DATALENGTH([NVarcharColumn_1]) + DATALENGTH([NVarcharColumn_N]) ) + 
   SUM( (DATALENGTH([VarcharColumn_1]) + DATALENGTH([VarcharColumn_N])) * 2 ) + 
   SUM(4 * [number_of_INT_columns]) +
   SUM(8 * [number_of_BIGINT_and_DATETIME_columns]) +
   SUM(16 * [number_of_DECIMAL/NUMERIC_and_UNIQUEIDENTIFIER_columns]) +
   etc..
FROM [SchemaName].[TableName] WITH (NOLOCK) -- assuming no Snapshot Isolation

Mas lembre-se, isso não leva em consideração a sobrecarga da coleção ou do elemento da coleção. E não tenho certeza se podemos obter esse valor sem um depurador (ou possivelmente algo como ILSpy, mas não estou recomendando isso, pois isso pode violar o EULA, dependendo das leis locais).

Solomon Rutzky
fonte
Acabamos implementando as verificações no código para garantir o tamanho do buffer, conforme apresentado ao aplicativo.
Tom V - tentativa topanswers.xyz
6

Da sua pergunta, parece que você tem um tamanho máximo de cache Se não deseja carregar tabelas no cache que excedam esse tamanho. Se isso for verdade, você não precisará saber o tamanho exato de cada tabela. Você só precisa saber se uma tabela é maior ou menor que o tamanho máximo do cache S. Esse é um problema significativamente mais fácil, dependendo das definições de coluna e contagem de linhas de suas tabelas.

Eu concordo com a grande resposta de Solomon Rutzky em que analisar dados não compactados não é o caminho a seguir e pode ser difícil apresentar uma boa aproximação para o tamanho real de uma tabela no cache. No entanto, vou trabalhar dentro da estrutura da pergunta e supor que você possa desenvolver uma fórmula suficientemente próxima, com base nas definições de coluna para tipos de dados estáticos e no comprimento real de suas colunas dinâmicas.

Se você tiver esse mapeamento de tipos de dados para armazenar em cache o tamanho, poderá avaliar algumas tabelas sem sequer olhar para os dados nelas:

  1. Se uma tabela tiver apenas tipos de dados estáticos (sem strings ou blobs), você poderá aproximar o número de linhas observando sys.partitionse calculando o tamanho da tabela usando definições de coluna.
  2. Se uma tabela com muitas linhas tiver colunas estáticas de tipo de dados suficientes, você poderá eliminá-la como muito grande sem examinar seus dados. Por exemplo, uma tabela com 10 milhões de linhas e 5 BIGINTcolunas pode ter o tamanho desses dados dimensionados como 10000000 * (8 + 8 + 8 + 8 + 8) = 400 M bytes, que podem ser maiores que o limite de tamanho do cache S. Não importa se ele também possui várias colunas de string.
  3. Se uma tabela com poucas linhas for pequena o suficiente, você poderá confirmar que está abaixo do limite simplesmente assumindo que cada tipo de dado dinâmico tenha o tamanho máximo possível. Por exemplo, uma tabela de 100 linhas com uma BIGINTcoluna e uma NVARCHAR(20)coluna não pode exceder 100 * (8 + 2 * 20) = 4800 bytes.
  4. Pode ser verdade que, se uma tabela tiver um tamanho compactado no SQL Server, isso é maior por algum fator S, é extremamente improvável que caiba no cache. Você precisaria fazer testes para descobrir se esse valor existe.
  5. Você pode ter sorte, pois todas as colunas dinâmicas têm estatísticas sobre elas. As estatísticas contêm informações sobre o comprimento médio e podem ser precisas o suficiente para seus propósitos.

Talvez você precise consultar os dados de tabelas que não se enquadram em nenhum dos critérios acima. Existem alguns truques que você pode usar para minimizar o impacto no desempenho disso. Eu diria que você tem duas prioridades concorrentes aqui: você valoriza a precisão, mas também não deseja verificar todos os dados em seu banco de dados. Pode ser possível adicionar algum tipo de buffer aos seus cálculos. Não sei se é mais aceitável excluir uma tabela que esteja ligeiramente abaixo do tamanho máximo de cache Sou incluir uma tabela que esteja ligeiramente acima do tamanho máximo de cache.

Aqui estão algumas idéias para acelerar as consultas que analisam os dados da tabela:

  1. Para tabelas grandes, você poderá usá-lo TABLESAMPLEenquanto o tamanho da amostra for grande o suficiente.
  2. Para tabelas grandes com uma chave em cluster, pode ser útil processá-las em lotes na chave em cluster. Infelizmente, não conheço uma maneira de calcular uma SUM()que saia cedo com base no valor desse agregado. Eu só vi esse trabalho para sempre ROW_NUMBER(). Mas você pode digitalizar os primeiros 10% da tabela, economizar o tamanho calculado dos dados, digitalizar os próximos 10% e assim por diante. Para tabelas muito grandes para o cache, você poderá salvar uma quantidade significativa de trabalho com essa abordagem ao sair mais cedo.
  3. Para algumas tabelas, você pode ter a sorte de ter índices de cobertura em todas as colunas dinâmicas. Dependendo do tamanho da linha ou de outros fatores, a varredura de cada índice por vez pode ser mais rápida do que a varredura de tabela. Você também pode encerrar esse processo mais cedo se o tamanho da tabela for muito grande depois de ler um índice em uma única coluna.
  4. Os comprimentos médios de suas colunas dinâmicas podem não estar mudando muito ao longo do tempo. Pode ser prático poupar os comprimentos médios que você calcula e usar esses valores em seus cálculos por um tempo. Você pode redefinir esses valores com base na atividade DML nas tabelas ou em alguma outra métrica.
  5. Se for possível executar testes em todas as tabelas para desenvolver um algoritmo, você poderá tirar proveito dos padrões nos dados. Por exemplo, se você processar tabelas começando pela menor, poderá descobrir que, depois de processar 10 tabelas (eu criei esse número) seguidas que são muito grandes para o cache, é muito improvável que tabelas maiores se encaixem no cache. Isso pode ser aceitável se for aceitável excluir algumas tabelas que possam caber no cache.

Percebo que não incluí nenhum código SQL nesta resposta. Deixe-me saber se seria útil escrever um código de demonstração para alguma das idéias que discuti aqui.

Joe Obbish
fonte
2
Eu não tinha pensado na abordagem de excluir tabelas como essa, eu gosto da abordagem
Tom V - tente topanswers.xyz